Webpack works for building JavaScript bundles, but does not support neither the HTML nor the CSS files out of the box.
To bundle compiled JavaScript and CSS with HTML we can use the powerful html-bundler-webpack-plugin.
Install
The first step is to install the plugin:
npm install html-bundler-webpack-plugin --save-dev
Install additional packages for styles:
npm install css-loader sass-loader sass --save-dev
A simple static site
We've created some static files to demonstrate how the plugin handles references in the HTML to script, style and image source files:
src/views/index.html <= entrypoint
src/js/main.js
src/scss/style.scss
src/images/favicon.ico
src/images/picture.png (size > 2 KB)
src/images/icon.png (size < 2 KB)
Create the index.html
file in the src/views/
directory:
<!doctype html>
<html lang="en">
<head>
<title>Home</title>
<link href="../images/favicon.ico" rel="shortcut icon">
<link href="../scss/style.scss" rel="stylesheet">
<script src="../js/main.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<img src="../images/picture.png" alt="picture">
<img src="../images/icon.png" alt="icon">
</body>
</html>
To bind our style and script files to HTML, we specify them directly in the HTML using relative paths to the source files.
With a complex file structure, some relative paths might be like ../../../images
. To avoid such unreadable paths, is recommended to use Webpack aliases.
The plugin allows us to use both relative paths and Webpack aliases.
We'll be using aliases, so we'll define them in the Webpack resolve.alias
option:
{
'@scripts': path.join(__dirname, 'src/js'),
'@styles': path.join(__dirname, 'src/scss'),
'@images': path.join(__dirname, 'src/images'),
}
Then change all relative paths in index.html
to the aliases:
<!doctype html>
<html lang="en">
<head>
<title>Home</title>
<link href="@images/favicon.ico" rel="shortcut icon">
<link href="@styles/style.scss" rel="stylesheet">
<script src="@scripts/main.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<img src="@images/picture.png" alt="picture">
<img src="@images/icon.png" alt="icon">
</body>
</html>
So HTML looks better and cleaner.
Processing HTML templates
Webpack doesn’t know how to handle HTML templates, but the html-bundler-webpack-plugin can render any template and place the generated HTML into the output directory.
Configure the plugin in the webpack.config.js
to handle index.html
:
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
entry: {
// define templates here
index: 'src/views/index.html', // => dist/index.html
},
}),
],
// ...
};
We can define HTML templates in the entry
option of the plugin.
The entry
key is the output filename without .html
extension, and the value is the template file. So we can define many templates in the entry
option.
Automatically processing many HTML templates
There is one template file, but it would be tedious to update the Webpack config every time templates are added or removed. Instead of manually adding other templates, let’s modify the configuration to automatically include all templates it finds.
module.exports = {
plugins: [
new HtmlBundlerPlugin({
// relative or absolute path to templates
entry: 'src/views/',
}),
],
// ...
};
If the entry
option is a path, the plugin finds all templates automatically and keep the same directory structure in the output directory. So we can create many templates in the entry directory, without restarting Webpack running in serve
mode.
Processing styles
To compile Sass files specified in HTML to CSS, add the module rule:
module.exports = {
// ...
module: {
rules: [
{
test: /\.(scss)$/,
use: ['css-loader', 'sass-loader'],
},
],
},
// ...
};
The plugin extracts CSS from compiled Sass files and save as a separate file into the output directory.
Processing images
The plugin resolves image references specified in HTML and allows Webpack to copy those images to the output directory. We can define a hashed output filename for images using the generator.filename
in the rule:
module.exports = {
// ...
module: {
rules: [
{
test: /\.(ico|png|jp?g|svg)$/,
type: 'asset/resource',
generator: {
filename: 'img/[name].[hash:8][ext]',
},
},
],
},
// ...
};
To optimise loading of small images we can inline images directly in the HTML using the type: 'asset'
and parser.dataUrlCondition.maxSize
in the rule:
module.exports = {
// ...
module: {
rules: [
{
test: /\.(ico|png|jp?g|svg)$/,
type: 'asset',
generator: {
// save images to file
filename: 'img/[name].[hash:8][ext]',
},
parser: {
dataUrlCondition: {
// inline images < 2 KB
maxSize: 2 * 1024
}
},
},
],
},
// ...
};
The plugin automatically inline Images smaller then maxSize
.
Output filenames for JS and CSS
By default, the processed JS and CSS files will be saved in the output directory under unhashed filenames. But it's recommended to use hashed output filenames.
We can define the output filenames for JS and CSS in the plugin options:
module.exports = {
plugins: [
new HtmlBundlerPlugin({
entry: {
index: 'src/views/index.html',
},
js: {
// output filename for JS
filename: 'js/[name].[contenthash:8].js',
},
css: {
// output filename for CSS
filename: 'css/[name].[contenthash:8].css',
},
}),
],
// ...
};
Optional, you can inline JS and CSS in HTML using the js.inline and the css.inline plugin options.
Live Reload
To enable reloading of the browser after changes, add the devServer
option to the Webpack config:
module.exports = {
// enable HMR with live reload
devServer: {
static: path.join(__dirname, 'dist'),
watchFiles: {
paths: ['src/**/*.*'],
options: {
usePolling: true,
},
},
},
};
Final Webpack configuration
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
mode: 'development',
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
},
resolve: {
alias: {
'@scripts': path.join(__dirname, 'src/js'),
'@styles': path.join(__dirname, 'src/scss'),
'@images': path.join(__dirname, 'src/images'),
},
},
plugins: [
new HtmlBundlerPlugin({
// path to templates
entry: 'src/views/',
js: {
// output filename for JS
filename: 'js/[name].[contenthash:8].js',
},
css: {
// output filename for CSS
filename: 'css/[name].[contenthash:8].css',
},
}),
],
module: {
rules: [
{
test: /\.(scss)$/,
use: ['css-loader', 'sass-loader'],
},
{
test: /\.(ico|png|jp?g|svg)/,
type: 'asset',
generator: {
// save images to file
filename: 'img/[name].[hash:8][ext]',
},
parser: {
dataUrlCondition: {
// inline images < 2 KB
maxSize: 2 * 1024,
},
},
},
],
},
// enable HMR with live reload
devServer: {
static: path.resolve(__dirname, 'dist'),
watchFiles: {
paths: ['src/**/*.*'],
options: {
usePolling: true,
},
},
},
};
Conclusion
The generated HTML contains the output filenames of the processed files and inlined small image. All processed files are paced in the dist/
output directory.
<!doctype html>
<html lang="en">
<head>
<title>Home</title>
<link href="img/favicon.34fd67a8.ico" rel="shortcut icon">
<link href="css/style.f042b772.css" rel="stylesheet">
<script src="js/main.b35246f4.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<img src="img/picture.7b396424.png" alt="picture">
<!-- src contains "data:image/png;base64,iVBORwSU..." -->
<img src="..." alt="icon">
</body>
</html>
With just one html-bundler-webpack-plugin you can very easily set up Webpack to generate static HTML files.
Top comments (1)
resolve alias @images doesnt work, i copied everything from here, but somehow the html cant resolve T_T.
edit: I changed my image from jpeg to png and now it works. the html bundler doesnt work with jpeg?
edit2: found the mistake, its this regex jp?g, it should be jpe?g