Using EJS with Vite
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.
How does it look like?
The HTML snippet is conveniently placed into a separate file with HTML/JS syntax highlightning 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 defineProject({
  ...
  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 
**/*.ejsto the list of included files in yourtsconfig.jsonfile. - Add the following type declaration to you 
global.d.tsfile: 
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!
