In 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.
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.
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.
webpack.config.js ------------ 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 D:\Git\multi-page-webpack-example\src\node_modules\webpack\bin\webpack.js 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 ironment. 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.
Perfect! HTML files are in the dist directory. Let's see the content of races.html and compare it to the original one.
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>
<head>
<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>
<body>
<h1>Previous races</h1>
<p>Choose edition to see results from a particular race.</p>
<ul id="races">
</ul>
</body>
</html>
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.
Summary
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.