Support runtime data injection to a static markup produced by HtmlWebpackPlugin, using EJS

Matti Bar-Zeev - Apr 23 '21 - - Dev Community

The requirements are simple

Let’s say you use Webpack HtmlWebpackPlugin to produce a static HTML file from a template you have, but sometimes that’s not enough. This page that you’re producing is that main static page for your SPA (Single Page Application) and aside from the processed bundles, versioning etc. you would like it to have other runtime data that concerns your entire site, for instance some sort of a configuration (not secret, god forbids) that needs to be available for scripts on the client.

The challenge

In most cases such data, as the configuration mentioned above, is runtime data derived from environment configuration, while the template used by the HtmlWebpackPlugin allows you to inject build-time data to it. This means that it wouldn’t be wise (or possible in cases) to use the template injection of HtmlWebpackPlugin for these purposes, so how do we go about it?

The common solution

Basically, when attempting to inject runtime data into a document that will be served to the client we turn to runtime templating engines. If your server runs on Node then a good option would be EJS. The server gets the request for the main document, loads the .ejs template file, renders it with the dynamic runtime data required and then returns it to the client.
So in theory we 3 phases here -

  1. Get HtmlWebpackPlugin to produce an .ejs file instead of .html file
  2. Upon request, fetch that .ejs template and render it with runtime data
  3. Return the result to the client

So how do we go about it?

Get HtmlWebpackPlugin to produce an .ejs file instead of .html file

This is quite simple, have your HtmlWebpackPlugin configuration define the result you want, like this:

new HtmlWebpackPlugin({
    ...
    template: './index.template.html',
    filename: 'index.ejs',
}),
Enter fullscreen mode Exit fullscreen mode

We declare where the template we’re using and the filename we wish it to have. Now when we run the build, the index.ejs file will be created according to the index.template.html file.

Upon request, fetch that .ejs template and render it with runtime data

Now, when we get the call for the doc we do this -

const doc = await ejs.renderFile('./dist/index.ejs', {conf:'chuck'}, {delimiter: '?'});
reply.send(doc);
Enter fullscreen mode Exit fullscreen mode

Here we take the .ejs template and render it with the dynamic runtime data which is defined on the current env (how to define it on your env and maintain prod and dev different configurations is a different story. If you wish me to dive into it, mention it in the comments).
This produces a doc which we can now send to the client.

Now, you might have noticed that “delimiter” property I passed to the ejs renderer. This is an important addition. You see, ejs is using the same default delimiter as the HtmlWebpackPlugin (“%”), and if you omit this property the HtmlWebpackPlugin will fail since it will try to interpolate keys in build time which are only available on runtime by a different templating engine. Therefore we use a different delimiter for ejs so there will not be any conflict.

That’s it. Be sure to leave you comments for any questions or feedback

Take care!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .