Using EJS with Vite
Konstantin Tarkus
Posted on June 15, 2023
Why Use EJS with Vite?
Let's consider an example scenario: you are building a web app that will run at CDN edge locations using Cloudflare Workers. In this scenario, you may have the following requirements:
- You need to configure the reverse proxy for certain third-party websites, such as Framer, Intercom Helpdesk, etc.
- You should be able to inject custom HTML/JS snippets into the pages of these websites.
- The code snippets should function correctly in different environments, such as production and test/QA.
- To optimize the application bundle, it is necessary to pre-compile these templates instead of including a template library.
In such cases, using EJS in combination with Vite can be a beneficial choice.
What does it look like?
The HTML snippet is conveniently placed into a separate file with HTML/JS syntax highlighting and code completion (views/analytics.ejs
):
<script async src="https://www.googletagmanager.com/gtag/js?id=<%- env.GA_MEASUREMENT_ID %>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "<%- env.GA_MEASUREMENT_ID %>");
</script>
While the Cloudflare Worker script injects it into an (HTML) landing page loaded from Framer:
import { Hono } from "hono";
import analytics from "../views/analytics.ejs";
export const app = new Hono<Env>();
// Serve landing pages, inject Google Analytics
app.use("*", async ({ req, env }, next) => {
const url = new URL(req.url);
// Skip non-landing pages
if (!["/", "/about", "/home"].includes(url.pathname)) {
return next();
}
const res = await fetch("https://example.framer.app/", req.raw);
return new HTMLRewriter()
.on("body", {
element(el) {
el.onEndTag((tag) => {
try {
tag.before(analytics(env), { html: true });
} catch (err) {
console.error(err);
}
});
},
})
.transform(res.clone());
});
How to pre-compile EJS templates with Vite?
Install ejs
and @types/ejs
NPM modules as development dependencies (yarn add ejs @types/ejs -D
).
Add the following plugin to your vite.config.ts
file:
import { compile } from "ejs";
import { readFile } from "node:fs/promises";
import { relative, resolve } from "node:path";
import { defineConfig } from "vite";
export default defineConfig({
...
plugins: [
{
name: "ejs",
async transform(_, id) {
if (id.endsWith(".ejs")) {
const src = await readFile(id, "utf-8");
const code = compile(src, {
client: true,
strict: true,
localsName: "env",
views: [resolve(__dirname, "views")],
filename: relative(__dirname, id),
}).toString();
return `export default ${code}`;
}
},
},
],
});
How to make .ejs
imports work with TypeScript?
- Add
**/*.ejs
to the list of included files in yourtsconfig.json
file. - Add the following type declaration to you
global.d.ts
file:
declare module "*.ejs" {
/**
* Generates HTML markup from an EJS template.
*
* @param locals an object of data to be passed into the template.
* @param escape callback used to escape variables
* @param include callback used to include files at runtime with `include()`
* @param rethrow callback used to handle and rethrow errors
*
* @return Return type depends on `Options.async`.
*/
const fn: (
locals?: Data,
escape?: EscapeCallback,
include?: IncludeCallback,
rethrow?: RethrowCallback,
) => string;
export default fn;
}
The kriasoft/relay-starter-kit
is a comprehensive full-stack web application project template that comes pre-configured with all the mentioned features (located in the /edge
folder).
If you require any assistance with web infrastructure and DevOps, feel free to reach out to me on Codementor or Discord. I'm here to help! Happy coding!
References
Posted on June 15, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.