Using EJS with Vite
Tags: webdev, javascript, vite, edge
Published: 2023-06-15 10:57:46

## 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`):

```html
<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:

```ts
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:

```ts
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?

1. Add `**/*.ejs` to the list of included files in your `tsconfig.json` file.
2. Add the following type declaration to you `global.d.ts` file:

```ts
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`](https://github.com/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](https://www.codementor.io/@koistya) or [Discord](https://discord.gg/bSsv7XM). I'm here to help! Happy coding!

## References

* https://ejs.co/
* https://vitejs.dev/
* https://developers.cloudflare.com/workers/
* https://github.com/kriasoft/relay-starter-kit