Hooking up HTML Preprocessors like EJS, Pug, & Handlebars

In this section we've been hooking up our favorite front end solutions and seeing how they interact with Webpack.

Next up are HTML templating languages like EJS, Pug and Handlebars.

They all pretty much hook up the same way.

To begin we're going to use the hookup branch as a starter for everything in this section. Since we won't be carrying over some of the changes into the Optimizing for Production section.

git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout hookup
npm install

EJS

Embedded Javascript is the most ruby like syntax. It's also the default for webpack HTMLPlugin

touch src/index.ejs

In your webpack.dev.js change the HTMLWebpackPlugin template to ejs.

new HTMLWebpackPlugin({
  template: "./src/index.ejs"
})

Copy and paste the entire index.html into index.ejs.

Okay, now that we're setup. let's add a variable to this template.

<html>
  <head>
    <title><%= htmlWebpackPlugin.options.title =%></title>
  </head>
  <body>
    <div class="profile">
      <img src="./images/link.jpg">
      <h1>Link's Journal</h1>
    </div>
  </body>
</html>

We have access to this template object htmlWebpackPlugin which literally is the plugin code, the options is an object of the same stuff that's in the webpack.dev.js. Let's add a title.

new HTMLWebpackPlugin({
  template: "./src/index.ejs",
  title: "Link's Journal"
})

We see the title. Which means, yes, there is a template variable, and you can really add whatever you want to this object and pass it through.

In addition to the options object, this global also holds a files object.

"htmlWebpackPlugin": {
  "files": {
    "css": [ "main.css" ],
    "js": [ "assets/main_bundle.js"],
    "chunks": {
      "main": {
        "entry": "assets/main_bundle.js",
        "css": []
      },
    }
  }
}

So yes, we can sort of hand inject certain scripts into this index.html at different points. Maybe that's useful to someone. I believe there are better ways to skin this particular cat, so I'm not going to teach this as a best practice.

Setting:

inject: false

in the plugin options, removes the automatic script and link tags and lets you choose how to add assets.

Assets in EJS

It seems that EJS loader doesn't parse images or other asset paths in the same way as the plain html loader. If we change the image loader option to include a hash.

test: /\.jpg$/,
use: [
  {
     loader: "file-loader",
     options: {
       name: "images/[name]-[hash:4].[ext]"
     }
  }
]

We see the image is broken in the browser.

Missing Image

There [is a workaround] library buit with lodash (https://github.com/emaphp/underscore-template-loader) to this for EJS style asset interpolation.

Put there's actually a slightly simpler way. In index.ejs, if you change the image line to require like so:

<img src="<%= require("./images/link.jpg") %>">

We will see the image comes thru with the proper handling. That's really the power of loaders. They introduce this require function into any file webpack handles. You can inline any asset, you can refer to any asset and webpack works it out.

Pug (Formerly Jade

Now let's do the same with Pug. Formerly Jade.

In terminal:

touch src/index.pug
npm install pug pug-loader

In src/index.pug

doctype html
html
  head
    title
      = htmlWebpackPlugin.options.title
  body
    .profile
      img(src=require("./images/link.jpg"))
      h1
        Hello Hyrule

In webpack.dev.js add the loader to handle pug files:

{
   test: /\.pug$/,
   use: [
      {
         loader: "pug-loader"
      }
   ]
}

In the new HTMLWebpackPlugin plugin options change the template to the pug file.

...
  template: "./src/index.pug",
...

Handlebars

Same goes for handlebars (.hbs files). I'll include the code in the final branch, but suffice it to say, webpack handles all of them very similarly.

In webpack.dev.js add a new loader:

{
   test: /\.hbs$/,
   use: [
      {
         loader: "handlebars-loader",
         query: { inlineRequires: '\/images\/' } }
      }
   ]
}

Handlebars requires specifics about what to include in inlineRequires. This query option tells it to include anything from the images folder. Pretty cool.

Update plugin options:

template: "./src/index.hbs",

Finally, add a new template file next to the rest.

touch src/index.hbs
npm install handlebars handlebars-loader
<html>
  <head>
    <title>{{ htmlWebpackPlugin.options.title }}</title>
  </head>
  <body>
    <div class="profile">
      <img src="./images/link.jpg"/>
      <h1>Link's Journal</h1>
    </div>
  </body>
</html>

In Sum

HTML Preprocessors are easily doable in webpack. The HTMLWebpackPlugin accommodates them all and even interpolates the images, same as HTML for all but pug. Hot-reloading the finished html on change once again becomes an issue with anything other than EJS, though I'm sure some Plugin author out there has solved that particular problem.

git checkout templates-final