Tutorial for webpack 4 - part 03 - HTML files in bundles

Webpack tutorial - part 03 - HTML files in bundles

webpackIn part 2 of the tutorial I built bundles from JS files. Although that is fundamental, it is not enough to create a package for an average project. Necessary element of each bundle is an HTML file. This part of the tutorial will present you how to include HTML in the bundles.


Table of contents

If you want to jump to other parts of the tutorial, use table of contents.


Current state

Currently, a user starts interacting with the application by entering races.html which contains the website structure and it loads races.js thanks to the script tag.

<script src="scripts/races.js"></script>

When a user choose a particular race, results.html page is loaded which requests results.js. The second part of the tutorial covered bundling races.js and results.js. Two files: races.html and results.html remained not bundled.

first bundles

I cannot deploy the application anywhere, yet. At least, I have to add the HTML files to the package.


HTML Webpack Plugin installation

We think about Webpack as a bundler but actually, it is more like a manager. It has very limited skills in doing a low level work but it can manage the whole process if it has skillful plugins somewhere close to it. An example of such work that Webpack needs a plugin for is bundling HTML files.

A popular solution in this area is HtmlWebpackPlugin. I install it in a similar way to other frontend libraries - with NPM.

D:\Git\multi-page-webpack-example>cd src

D:\Git\multi-page-webpack-example\src>npm install html-webpack-plugin --save-dev
npm WARN multi-page-webpack-example@1.0.0 No description
npm WARN multi-page-webpack-example@1.0.0 No repository field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ html-webpack-plugin@3.2.0
added 51 packages from 67 contributors and audited 4336 packages in 23.158s
found 0 vulnerabilities

I can see that HtmlWebpackPlugin was added as a dependency to package.json.

  "name": "multi-page-webpack-example",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.27.1",
    "webpack-cli": "^3.1.2"

Package.json helps managing external libraries and their versions for the project.


Html Webpack Plugin configuration

Assuming that everything went well with the plugin installation, I have to configure it. I add new entries to webpack.config.js which should be familiar to you from the previous parts of the tutorial. This is where most of the configuration related to Webpack should be. Plugins have a separate section named plugins.

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
races: "./main/webapp/scripts/races.js",
results: "./main/webapp/scripts/results.js",
output: {
path: __dirname + "/main/webapp/dist/",
filename: "scripts/[name].bundle.js"
plugins: [
new HtmlWebpackPlugin({
template: "./main/webapp/races.html",
filename: "races.html",
inject: 'head',
chunks: ['races']
new HtmlWebpackPlugin({
template: "./main/webapp/results.html",
filename: "results.html",
inject: 'head',
chunks: ['results']

The section is an array which consists of two elements in my case. The first one instantiates HtmlWebpackPlugin for races.html. The template option points to the original HTML file which should be bundled. Actually, it does not have to be a complete HTML file. It could be just a template but let's do not worry about it now.

Filename is a name of an HTML file that will be generated.

Inject option allows telling the plugin where the dependencies should be inserted to the generated HTML file. In my case it is head which means that a script tag with a URL to races.js should be added to the head tag.

The last option I used - chunk is a list of chunks that are dependencies for the particular template. If you do not remember, chunks are defined in the entry section and there are two of them in my application: races and results. Thanks to the current configuration, generated races.html will have only one script tag that will point to races.js. If I did not use the chunk option, both chunks would be treated as dependencies so races.html would require races.js and results.js. I used the option so races.html requests only races.js.

The second element instantiates HtmlWebpackPlugin for results.html. All options are analogical to the first element described above so I will not elaborate.


Running Webpack

Webpack configuration seems ready so it is time to run Webpack. I do it in the same way as in the previous article with NPX:

D:\Git\multi-page-webpack-example\src>npx webpack
npx: installed 1 in 16.704s
The "path" argument must be of type string
Hash: 59f048f629a0e2aba36f
Version: webpack 4.27.1
Time: 1608ms
Built at: 2019-01-06 19:45:31
                    Asset       Size  Chunks             Chunk Names
               races.html  411 bytes          [emitted]
             results.html  619 bytes          [emitted]
  scripts/races.bundle.js    1.2 KiB       0  [emitted]  races
scripts/results.bundle.js   1.55 KiB       1  [emitted]  results
Entrypoint races = scripts/races.bundle.js
Entrypoint results = scripts/results.bundle.js
[0] ./main/webapp/scripts/races.js 502 bytes {0} [built]
[1] ./main/webapp/scripts/results.js 1.33 KiB {1} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each env
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "races.html":
     1 asset
    Entrypoint undefined = races.html
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./main/webapp/races.html 568 bytes {0} [built]
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 1 hidden module
Child html-webpack-plugin for "results.html":
     1 asset
    Entrypoint undefined = results.html
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./main/webapp/results.html 796 bytes {0} [built]
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 1 hidden module

Output of the command says that both HTML and JS files were generated for each entry point but I will devote a few seconds of my time to confirm it.

HTML and JS generated

Perfect! HTML files are in the dist directory. Let's see the content of races.html and compare it to the original one.

HTML original vs generated

That is pretty much what I wanted. The file in the generated package has a script tag pointing to scripts/races.bundle.js which is the generated version as well. Putting my happiness aside, one line is a trouble maker here. It is the script tag pointing to scripts/races.js. It was directly copied from the original races.html file but it does not make sense in the generated one. The whole purpose of building content of the dist directory is deploying it to production. None of the elements should require development files like scripts/races.js. The package should be autonomous. It is a broader subject but for now it is enough to just manually remove that line to have the following content:

<!DOCTYPE html>
meta charset="utf-8">
meta http-equiv="X-UA-Compatible" content="IE=edge">
title>Previous races</title>
script type="text/javascript" src="scripts/races.bundle.js"></script></head>

h1>Previous races</h1>
p>Choose edition to see results from a particular race.</p>
ul id="races">


Do the same with results.html and you can deploy the content of the dist directory. I use IntelliJ IDEA so I do right mouse button click on the dist/races.html file and choose Run 'races.html(1)'. I can click through the web application to verify that everything works as expected.



Today, I configured Webpack to add HTML files to the generated package. Moreover, the HTML files correctly depend on the generated JS script.

Source code created in this part is on tutorial/part03-HTML-files branch on GitHub


Next part

Webpack tutorial - part 04 - Modes