DEV Community

Konstantin Tarkus
Konstantin Tarkus

Posted on

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.

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>
Enter fullscreen mode Exit fullscreen mode

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());
});
Enter fullscreen mode Exit fullscreen mode

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}`;
        }
      },
    },
  ],
});
Enter fullscreen mode Exit fullscreen mode

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:
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;
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)