Package Exports
- html-bundler-webpack-plugin
- html-bundler-webpack-plugin/src/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (html-bundler-webpack-plugin) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
HTML Bundler Plugin for Webpack
The plugin make Webpack setup easily and intuitive.
This plugin allows to use an HTML template as a starting point for all dependencies used in your web application. All source styles, scripts specified in HTML are processed, and the extracted JS, CSS are saved to the output directory. The plugin automatically substitutes output filenames into the generated HTML file.
💡 Highlights
- An entry point is an HTML template.
- Source scripts and styles can be loaded directly in HTML using
<script>
and<link>
tags. - All JS and CSS files extracts from their sources specified in HTML.
- You can inline JS, CSS, SVG, PNG without additional plugins and loaders.
- You can use a template engine like EJS, Handlebars, Nunjucks and others without template loaders.
- This plugin works like the pug-plugin but the entry point is a
HTML
template.
How to create multiple HTML pages with html-bundler-webpack-plugin, see the Webpack boilerplate.
If you have discovered a bug or have a feature suggestion, feel free to create an issue on GitHub.
Simple usage example
Add source scripts, styles, images directly to HTML using a relative path or a Webpack alias:
<html>
<head>
<!-- load source style here -->
<link href="./style.scss" rel="stylesheet">
<!-- load source script here -->
<script src="./main.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<!-- @images is the Webpack alias for the source images directory -->
<img src="@images/logo.png">
</body>
</html>
The generated HTML contains hashed output filenames of processed source files:
<html>
<head>
<link href="/assets/css/style.05e4dd86.css" rel="stylesheet">
<script src="/assets/js/main.f4b855d8.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<img src="/assets/img/logo.58b43bd8.png">
</body>
</html>
Add the HTML templates in the entry
option (syntax is identical to Webpack entry):
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
resolve: {
alias: {
'@images': path.join(__dirname, 'src/images'),
},
},
plugins: [
new HtmlBundlerPlugin({
entry: {
// define HTML templates here
index: 'src/views/home/index.html', // output dist/index.html
},
}),
],
module: {
rules: [
{
test: /.html/,
loader: HtmlBundlerPlugin.loader, // HTML template loader
},
// ... other rules, e.g. for styles, images, fonts, etc.
],
},
};
Contents
- Features
- Install and Quick start
- Plugin options
- test (process only templates matching RegExp)
- entry (define HTML templates)
- outputPath (output path of HTML file)
- filename (output filename of HTML file)
- css (output filename of extracted CSS)
- js (output filename of extracted JS)
- postprocess
- extractComments
- verbose
- Loader options
- sources (processing of custom tag attributes)
- preprocessor (templating)
- Setup HMR live reload
- Recipes
- How to use source images in HTML
- How to resize and generate responsive images
- How to preload source fonts in HTML
- How to inline CSS in HTML
- How to inline JS in HTML
- How to inline SVG, PNG images in HTML
- How to use a template engine
- How to pass data into templates
- How to config
splitChunks
- How to split multiple node modules and save under own names
Features
- HTML template is the entry point for all resources (styles, scripts)
- extracts CSS from source style loaded in HTML via a
<link>
tag - extracts JS from source script loaded in HTML via a
<script>
tag - resolves source files in the CSS
url()
and in HTML attributes - extracts resolved resources to output directory
- generated HTML contains hashed CSS, JS, images, fonts output filenames
- support the module types
asset/resource
asset/inline
asset
inline CSS
in HTMLinline JavaScript
in HTMLinline image
asbase64 encoded
data-URL for PNG, JPG, etc. in HTML and CSSinline SVG
as SVG tag in HTMLinline SVG
asutf-8
data-URL in CSS- support the
auto
publicPath - enable/disable extraction of comments to
*.LICENSE.txt
file - supports all template engines for Node.js like EJS, Handlebars, Nunjucks and others
Just one HTML bundler plugin replaces the most used functionality of the plugins and loaders:
Package | Features |
---|---|
html-webpack-plugin | creates HTML and inject script tag for compiled JS file into HTML |
mini-css-extract-plugin | injects link tag for processed CSS file into HTML |
webpack-remove-empty-scripts | removes generated empty JS files |
html-loader | exports HTML |
style-loader | injects an inline CSS into HTML |
posthtml-inline-svg | injects an inline SVG icon into HTML |
resolve-url-loader | resolves a relative URL in CSS |
svg-url-loader | encodes a SVG data-URL as utf8 |
Install and Quick start
Install the html-bundler-webpack-plugin
:
npm install html-bundler-webpack-plugin --save-dev
Install additional packages for styles:
npm install css-loader sass sass-loader --save-dev
Change your webpack.config.js
according to the following minimal configuration:
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
publicPath: '/',
},
plugins: [
new HtmlBundlerPlugin({
entry: {
// define HTML files here
index: 'src/views/home/index.html', // output dist/index.html
'pages/about': 'src/views/about/index.html', // output dist/pages/about.html
// ...
},
js: {
// output filename of extracted JS from source script loaded in HTML via `<script>` tag
filename: 'assets/js/[name].[contenthash:8].js',
},
css: {
// output filename of extracted CSS from source style loaded in HTML via `<link>` tag
filename: 'assets/css/[name].[contenthash:8].css',
},
}),
],
module: {
rules: [
{
test: /\.html$/,
loader: HtmlBundlerPlugin.loader, // HTML template loader
},
{
test: /\.(css|sass|scss)$/,
use: ['css-loader', 'sass-loader'],
},
],
},
};
Note
Since the version
0.9.0
, you can define HTML templates in theentry
option of the plugin. If is used theentry
option of the plugin, then the origin Webpackentry
option should be undefined.
Plugin options
test
Type: RegExp
Default: /\.html$/
The test
option allows то handel only those entry points that match their source filename.
For example, if you has *.html
and *.hbs
entry points, then you can set the option to match all needed files: test: /\.(html|hbs)$/
.
entry
Type: object
is identical to Webpack entry
plus additional data
property.
Define your HTML files or templates in the entry option.
HTML is a starting point for collecting all the dependencies used in your web application. Specify source scripts (JS, TS) and styles (CSS, SCSS, etc.) directly in HTML. The plugin automatically extracts JS, CSS from their sources specified in HTML.
Simple syntax
The key of an entry object is the output file
w/o extension, relative by the outputPath
option.
The value is the source file
, absolute or relative by the Webpack config file.
{
entry: {
index: 'src/views/home/index.html', // => dist/index.html
'pages/about/index': 'src/views/about.html', // => dist/pages/about/index.html
},
}
Advanced syntax
The entry value might be an object:
type entryValue = {
import: string,
filename: string
data: object,
}
import
- a source file, absolute or relative by the Webpack config filefilename
- an output file, relative by the 'outputPath' optiondata
- an object passed intopreprocessor
to render a template with variables
Usage example:
{
entry: {
'pages/about/index': { // output file as the key
import: 'src/views/about.html', // source template file
data: {
title: 'About',
}
},
contact: {
import: 'src/views/contact.html',
filename: 'pages/contact/index.html', // output file as the 'filename' property
},
},
}
Note
You can define templates both in Webpack
entry
and in theentry
option of the plugin. The syntax is identical. But thedata
property can only be defined in theentry
option of the plugin.
outputPath
Type: string
Default: webpack.options.output.path
The output directory for processed file. This directory can be relative by webpack.options.output.path
or absolute.
filename
Type: string | Function
Default: [name].html
The output filename relative by the outputPath
option.
- If type is
string
then following substitutions (see output.filename for chunk-level) are available in template string:[id]
The ID of the chunk.[name]
Only filename without extension or path.[contenthash]
The hash of the content.[contenthash:nn]
Thenn
is the length of hashes (defaults to 20).
- If type is
Function
then following arguments are available in the function:@param {PathData} pathData
has the useful properties (see the type PathData):pathData.filename
the full path to source filepathData.chunk.name
the name of entry key
@param {AssetInfo} assetInfo
Mostly this object is empty.@return {string}
The name or template string of output file.
css
Type: Object
Default properties:
{
test: /\.(css|scss|sass|less|styl)$/,
filename: '[name].css',
outputPath: null,
verbose: false,
}
test
- an RegEpx to process all source styles that pass test assertionfilename
- an output filename of extracted CSS. Details see by filename option.\outputPath
- an output path of extracted CSS. Details see by outputPath option.verbose
- enable/disable display process information for styles
This is the option to extract CSS from a style source file specified in the HTML tag:
<link href="./style.scss" rel="stylesheet">
Warning
Don't import source styles in JavaScript! Styles must be specified directly in HTML. Don't define source JS files in Webpack entry! Scripts must be specified directly in HTML.
The default CSS output filename is [name].css
.
You can specify your own filename using webpack filename substitutions:
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
css: {
filename: 'assets/css/[name].[contenthash:8].css',
},
}),
],
};
The [name]
is the base filename of a loaded style.
For example, if source file is style.scss
, then output filename will be assets/css/style.1234abcd.css
.
If you want to have a different output filename, you can use the filename
options as the function.
Warning
Don't use
mini-css-extract-plugin
orstyle-loader
, they are not required more.
Thehtml-bundler-webpack-plugin
extracts CSS much faster than other plugins and resolves all asset URLs in CSS, therefore theresolve-url-loader
is redundant too.
js
Type: Object
Default properties:
{
filename: '[name].js',
outputPath: null,
verbose: false,
}
filename
- an output filename of extracted JS. Details see by filename option.\outputPath
- an output path of extracted CSS. Details see by outputPath option.verbose
- enable/disable display process information for styles
The test
property absent because all JS files specified in <script>
tag are automatically detected.
This is the option to extract JS from a script source file specified in the HTML tag:
<script src="./main.js"></script>
The default JS output filename is [name].js
.
You can specify your own filename using webpack filename substitutions:
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
js: {
filename: 'assets/js/[name].[contenthash:8].js',
},
}),
],
};
The [name]
is the base filename script.
For example, if source file is main.js
, then output filename will be assets/js/main.1234abcd.js
.
If you want to have a different output filename, you can use the filename
options as the function.
postprocess
Type:
type postprocess = (
content: string,
info: ResourceInfo,
compilation: Compilation,
) => string|null;
type ResourceInfo = {
verbose: boolean,
isEntry: boolean,
filename:
| string
| ((pathData: PathData) => string),
sourceFile: string,
outputPath: string,
assetFile: string,
};
Default: null
Called after the Webpack compilation.
The postprocess
have the following arguments:
content: string
- a content of processed fileinfo: ResourceInfo
- an info about current filecompilation: Compilation
- the Webpack compilation object
If return null
then the content processed via Webpack is ignored and will be saved a result from the loader.
The ResourceInfo
have the following properties:
verbose: boolean
- whether information should be displayedisEntry: boolean
- if istrue
, the resource is the entry point, otherwise is a resource loaded in the entry pointfilename: string|function
- a filename of the resource, see filenamesourceFile: string
- a full path of the source fileoutputPath: string
- a full path of the output directoryassetFile: string
- an output asset file relative by outputPath
extractComments
Type: boolean
Default: false
Enable / disable extraction of comments to *.LICENSE.txt
file.
When using splitChunks
optimization for node modules containing comments,
Webpack extracts those comments into a separate text file.
By default, the plugin don't create such unwanted text files.
But if you want to extract files like *.LICENSE.txt
, set this option to true
:
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
extractComments: true,
}),
],
};
verbose
Type: boolean
Default: false
Display information about all processed files.
Loader options
sources
Type:
type sources =
| boolean
| Array<{
tag?: string;
attributes?: Array<string>;
filter?: ({
tag: string,
attribute: string,
value: string,
attributes: string,
resourcePath: string
}) => boolean|undefined;
}>;
Default: true
By default, resolves source files in the following tags and attributes:
Tag | Attributes |
---|---|
link |
href (for type="text/css" or rel="stylesheet" ) imagesrcset (for as="image" ) |
script |
src |
img |
src srcset |
input |
src (for type="image" ) |
source |
src srcset |
audio |
src |
track |
src |
video |
src poster |
object |
data |
To disable the processing of all attributes, set the sources
option as false
.
Note
Automatically are processed only attributes containing a relative path or Webpack alias:
src="./image.png"
- a relative path to local directorysrc="../../assets/image.png"
- a relative path to parent directorysrc="@images/image.png"
- an image directory as Webpack aliasUrl values are not processed:
src="https://example.com/img/image.png"
src="//example.com/img/image.png"
src="/img/image.png"
Others not file values are ignored, e.g.:
src="data:image/png; ..."
src="javascript: ..."
The filter
is called for all attributes of the tag defined as defaults and in sources
option.
The argument is an object containing the properties:
tag: string
- a name of the HTML tagattribute: string
- a name of the HTML attributevalue: string
- a value of the HTML attributeattributes: string
- all attributes of the tagresourcePath: string
- a path of the HTML template
The processing of an attribute can be ignored by returning false
.
Examples of using argument properties:
{
tag: 'img',
// use the destructuring of variables from the object argument
filter: ({ tag, attribute, value, attributes, resourcePath }) => {
if (attribute === 'src') return false;
if (value.endsWith('.webp')) return false;
if ('srcset' in attributes && attributes['srcset'] === '') return false;
if (resourcePath.indexOf('example')) return false;
// otherwise return 'true' or nothing to allow processing
},
}
The default sources can be extended with new tags and attributes.
For example, add the processing of the data-src
and data-srcset
attributes to the img
tag:
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.html$/,
loader: HtmlBundlerPlugin.loader,
options: {
sources: [
{
tag: 'img',
attributes: ['data-src', 'data-srcset'],
}
],
},
},
],
},
};
You can use the filter
function to allow the processing only specific attributes.
For example, allow processing only for images in content
attribute of the meta
tag:
<html>
<head>
<!-- ignore the attribute via filter -->
<meta name="theme-color" content="#ffffff">
<!-- resolve the 'content' attribute if 'name' containing special values -->
<meta name="twitter:image" content="./image.png">
<meta name="logo" content="./logo.png">
</head>
<body>
<!-- resolve 'src' attribute containing relative path -->
<img src="./image.png">
</body>
</html>
webpack.config.js
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.html$/,
loader: HtmlBundlerPlugin.loader,
options: {
sources: [
{
tag: 'meta',
attributes: ['content'],
filter: ({ attributes }) => {
const allowedNames = ['twitter:image', 'logo'];
if ('name' in attributes && allowedNames.indexOf(attributes.name) < 0) {
return false;
}
},
}
],
},
},
],
},
};
The filter can disable an attribute of a tag.
For example, disable the processing of default attribute srcset
of the img
tag:
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.html$/,
loader: HtmlBundlerPlugin.loader,
options: {
sources: [
{
tag: 'img',
filter: ({ attribute }) => attribute !== 'srcset',
}
],
},
},
],
},
};
preprocessor
Type:
type preprocessor = (
content: string,
loaderContext: LoaderContext
) => HTMLElement;
Default: undefined
The content
is the raw content of a file.
The loaderContext
is the Loader Context object contained useful properties:
mode: string
- a Webpack mode:production
,development
,none
rootContext: string
- a path to Webpack contextresource: string
- a template file, including queryresourcePath: string
- a template filedata: object|null
- variables passed formentry
The preprocessor is called for each entry file, before handling of the content. This function can be used to compile the template with a template engine, such as EJS, Handlebars, Nunjucks, etc.
For example, set a variable in the template index.html
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Webpack config
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
},
plugins: [
new HtmlBundlerPlugin({
entry: {
index: { // => dist/index.html
import: './src/views/index.html',
data: {
title: 'Homepage',
}
},
},
}),
],
module: {
rules: [
{
test: /\.html$/,
loader: HtmlBundlerPlugin.loader,
options: {
preprocessor: (content, { data }) => {
// you can use here a template engine like EJS, Handlebars, Nunjucks, etc.
return content.replace('{{ title }}', data.title);
},
},
},
],
},
};
See the example How to use a template engine.
Setup HMR live reload
To enable live reload by changes any file add in the Webpack config the devServer
option:
module.exports = {
// enable HMR with live reload
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
watchFiles: {
paths: ['src/**/*.*'],
options: {
usePolling: true,
},
},
},
};
Note
Live reload works only if in HTML used a JS file. This is specific of Webpack. If your HTML has not a JS, then create one empty JS file, e.g.
hmr.js
and add it in the HTML:<script src="./hmr.js"></script>
How to use source images in HTML
Add to Webpack config the rule:
module: {
rules: [
{
test: /\.(png|jpe?g|ico|svg)$/,
type: 'asset/resource',
generator: {
filename: 'assets/img/[name].[hash:8][ext]',
},
},
],
}
Add a source file using a relative path or Webpack alias in HTML:
<html>
<head>
<link href="./favicon.ico" rel="icon" />
</head>
<body>
<img src="./apple.png" srcset="./apple1.png 320w, ./apple2.png 640w" alt="apple">
<picture>
<source srcset="./fig1.jpg, ./fig2.jpg 320w, ./fig3.jpg 640w">
</picture>
</body>
</html>
The generated HTML contains hashed output images filenames:
<html>
<head>
<link href="/assets/img/favicon.05e4dd86.ico" rel="icon" />
</head>
<body>
<img src="/assets/img/apple.f4b855d8.png" srcset="/assets/img/apple1.855f4bd8.png 320w, /assets/img/apple2.d8f4b855.png 640w" alt="apple">
<picture>
<source srcset="/assets/img/fig1.605e4dd8.jpg, /assets/img/fig2.8605e4dd.jpg 320w, /assets/img/fig3.e4605dd8.jpg 640w">
</picture>
</body>
</html>
How to resize and generate responsive images
To resize or generate responsive images is recommended to use the responsive-loader.
Install additional packages:
npm i -D responsive-loader sharp
To resize an image use the query parameter size
:
<!-- resize source image to max. 640px -->
<img src="./image.png?size=640">
To generate responsible images use in srcset
attribute the query parameter sizes
als JSON5
to avoid parsing error,
because many images must be separated by commas ,
but we use the comma to separate sizes for one image:
<!-- responsible images with different sizes: 320px, 480px, 640px -->
<img src="./image.png?size=480"
srcset="./image.png?{sizes:[320,480,640]}">
You can convert source image to other output format. For example, we have original image 2000px width as PNG and want to resize to 640px and save as WEBP:
<img src="./image.png?size=640&format=webp">
You can create a small inline image placeholder. To do this, use the following query parameters:
placeholder=true
- enable to generate the placeholderplaceholderSize=35
- the size of the generating placeholderprop=placeholder
- the plugin-specificprop
parameter retrieves the property from the object generated byresponsive-loader
<img src="./image.png?placeholder=true&placeholderSize=35&prop=placeholder"
srcset="./image.png?{sizes:[320,480,640]}">
The generated HTML:
<img src="data:image/png;base64,iVBORw0K ..."
srcset="/img/image-320w.png 320w,/img/image-480w.png 480w,/img/image-640w.png 640w">
Add to Webpack config the rule for responsive images:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|webp)$/,
type: 'asset/resource',
use: {
loader: 'responsive-loader',
options: {
// output filename of images, e.g. dist/assets/img/image-640w.png
name: 'assets/img/[name]-[width]w.[ext]',
sizes: [640], // max. image size, if 'size' query is not used
},
},
},
// ... other loaders
],
},
};
How to preload source fonts in HTML
Add to Webpack config the rule:
module: {
rules: [
{
test: /\.(eot|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'assets/fonts/[name][ext]',
},
},
],
}
Add a source file using a relative path or Webpack alias in HTML:
<html>
<head>
<link href="./font1.woff2" rel="preload" as="font" type="font/woff2" />
<link href="./font2.woff2" rel="preload" as="font" type="font/woff2" />
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
The generated HTML contains output fonts filenames:
<html>
<head>
<link href="/assets/fonts/font1.woff2" rel="preload" as="font" type="font/woff2" />
<link href="/assets/fonts/font2.woff2" rel="preload" as="font" type="font/woff2" />
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Note
You don't need a plugin to copy files from source directory to public.
How to inline CSS in HTML
For example, the style.scss:
$color: red;
h1 {
color: $color;
}
Add the ?inline
query to the source filename which you want to inline:
<html>
<head>
<!-- load style as file -->
<link href="./main.scss" rel="stylesheet" />
<!-- inline style -->
<link href="./style.scss?inline" rel="stylesheet" />
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
The generated HTML contains inline CSS already processed via Webpack:
<html>
<head>
<!-- load style as file -->
<link href="/assets/css/main.05e4dd86.css" rel="stylesheet">
<!-- inline style -->
<style>
h1{color: red;}
</style>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Note
To enable source map in inline CSS set the Webpack option
devtool
.
How to inline JS in HTML
For example, the script.js:
console.log('Hello JS!');
Add the ?inline
query to the source filename which you want to inline:
<html>
<head>
<!-- load script as file -->
<script src="./main.js" defer="defer"></script>
<!-- inline script -->
<script src="./script.js?inline"></script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
The generated HTML contains inline JS already compiled via Webpack:
<html>
<head>
<!-- load style as file -->
<script src="assets/js/main.992ba657.js" defer="defer"></script>
<!-- inline script -->
<script>
(()=>{"use strict";console.log("Hello JS!")})();
</script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Note
If Webpack is started as
serve
orwatch
, the inlined JS code will contain additional HMR code. Don't worry it is ok, so works Webpacklive reload
.To enable source map in inline JS set the Webpack option
devtool
.
How to inline SVG, PNG images in HTML
You can inline the images in two ways:
- force inline image using
?inline
query - auto inline by image size
Add to Webpack config the rule:
module: {
rules: [
{
test: /\.(png|jpe?g|svg|webp|ico)$/i,
oneOf: [
// inline image using `?inline` query
{
resourceQuery: /inline/,
type: 'asset/inline',
},
// auto inline by image size
{
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 1024,
},
},
generator: {
filename: 'assets/img/[name].[hash:8][ext]',
},
},
],
},
],
}
How to use a template engine
Using the preprocessor you can compile the template with a template engine such as:
Note
For Pug templates use the pug-plugin. This plugin works on the same codebase but has additional Pug-specific features.
Using the Handlebars
For example, there is a template index.hbs
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ headline }}</h1>
<div>
<p>{{ firstname }} {{ lastname }}</p>
</div>
</body>
</html>
Add the preprocessor
option to compile the template.
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const Handlebars = require('handlebars');
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
},
plugins: [
new HtmlBundlerPlugin({
test: /\.(html|hbs)$/, // add the option to match *.hbs files in entry, default is /\.html$/
entry: {
index: {
import: './src/views/home/index.hbs',
// pass data into the preprocessor
data: {
title: 'My Title',
headline: 'Breaking Bad',
firstname: 'Walter',
lastname: 'Heisenberg',
},
},
},
}),
],
module: {
rules: [
{
test: /\.(html|hbs)$/, // must match the files specified in the entry
loader: HtmlBundlerPlugin.loader,
options: {
// add the preprocessor function to compile *.hbs files to HTML
preprocessor: (content, { data }) => Handlebars.compile(content)(data),
},
},
],
},
};
Note
If you are using a template with a specific extension other than
.html
, specify that extension in thetest
option of the plugin.For example, when using the Handlebars template with
.hbs
extension:new HtmlBundlerPlugin({ test: /.hbs$/, entry: { index: './src/views/home.hbs', // <= specify all extensions used here in the 'test' option }, })
Using the Mustache
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const Mustache = require('mustache');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
test: /\.mustache$/, // <= change
entry: {
index: './src/views/home.mustache'
},
}),
],
module: {
rules: [
{
test: /\.mustache$/, // <= change
loader: HtmlBundlerPlugin.loader,
options: {
preprocessor: (content, { data }) => Mustache.render(content, data), // <= change
},
},
],
},
};
Using the Nunjucks
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const Nunjucks = require('nunjucks');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
test: /\.njk$/, // <= change
entry: {
index: './src/views/home.njk'
},
}),
],
module: {
rules: [
{
test: /\.njk$/, // <= change
loader: HtmlBundlerPlugin.loader,
options: {
preprocessor: (content, { data }) => Nunjucks.renderString(content, data), // <= change
},
},
],
},
};
Using the EJS
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const ejs = require('ejs');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
test: /\.ejs$/, // <= change
entry: {
index: './src/views/home.hbs'
},
}),
],
module: {
rules: [
{
test: /\.ejs$/, // <= change
loader: HtmlBundlerPlugin.loader,
options: {
preprocessor: (content, { data }) => ejs.render(content, data), // <= change
},
},
],
},
};
How to pass data into templates
You can pass variables into template using a template engine, e.g. Handlebars. For multiple page configuration, better to use the Nunjucks template engine maintained by Mozilla.
For example, you have several pages with variables.
Both pages have the same layout src/views/layouts/default.html
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<!-- block for specific page styles -->
{% block styles %}{% endblock %}
<!-- block for specific page scripts -->
{% block scripts %}{% endblock %}
</head>
<body>
<main class="main-content">
<!-- block for specific page content -->
{% block content %}{% endblock %}
</main>
</body>
</html>
src/views/pages/home/index.html
{% extends "src/views/layouts/default.html" %}
{% block styles %}
<!-- load source style -->
<link href="./home.scss" rel="stylesheet">
{% endblock %}
{% block scripts %}
<!-- load source script -->
<script src="./home.js" defer="defer"></script>
{% endblock %}
{% block content %}
<h1>{{ filmTitle }}</h1>
<p>Location: {{ location }}</p>
<!-- @images is the Webpack alias for the source images directory -->
<img src="@images/{{ imageFile }}">
{% endblock %}
src/views/pages/about/index.html
{% extends "src/views/layouts/default.html" %}
{% block styles %}
<link href="./about.scss" rel="stylesheet">
{% endblock %}
{% block scripts %}
<script src="./about.js" defer="defer"></script>
{% endblock %}
{% block content %}
<h1>Main characters</h1>
<ul>
{% for item in actors %}
<li class="name">{{ item.firstname }} {{ item.lastname }}</li>
{% endfor %}
</ul>
{% endblock %}
Webpack config
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const Nunjucks = require('nunjucks');
// Note:
// If your pages have a lot of variables, it's a good idea to define them separately
// to keep the configuration clean and clear.
const entryData = {
// variables for home page
home: {
title: 'Home',
filmTitle: 'Breaking Bad',
location: 'Albuquerque, New Mexico',
imageFile: 'map.png',
},
// variables for about page
about: {
title: 'About',
actors: [
{
firstname: 'Walter',
lastname: 'White, "Heisenberg"',
},
{
firstname: 'Jesse',
lastname: 'Pinkman',
},
],
},
};
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
},
resolve: {
alias: {
'@images': path.join(__dirname, 'src/assets/images'),
},
},
plugins: [
new HtmlBundlerPlugin({
entry: {
// define your templates here
index: { // => dist/index.html
import: 'src/views/pages/home/index.html',
data: entryData.home,
},
about: { // => dist/about.html
import: 'src/views/pages/about/index.html',
data: entryData.about,
},
},
js: {
filename: 'assets/js/[name].[contenthash:8].js',
},
css: {
filename: 'assets/css/[name].[contenthash:8].css',
},
}),
],
module: {
rules: [
// templates
{
test: /\.html/,
loader: HtmlBundlerPlugin.loader, // HTML template loader
options: {
// render template with page-specific variables defined in entry
preprocessor: (content, { data }) => Nunjucks.renderString(content, data),
},
},
// styles
{
test: /\.(css|sass|scss)$/,
use: ['css-loader', 'sass-loader'],
},
// images
{
test: /\.(png|svg|jpe?g|webp)$/i,
type: 'asset/resource',
generator: {
filename: 'assets/img/[name].[hash:8][ext]',
},
},
],
},
};
The generated dist/index.html
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<link href="assets/css/home.2180238c.css" rel="stylesheet">
<script src="assets/js/home.790d746b.js" defer="defer"></script>
</head>
<body>
<main class="main-content">
<h1>Breaking Bad</h1>
<p>Breaking Bad is an American crime drama</p>
<p>Location: Albuquerque, New Mexico</p>
<img src="assets/img/map.697ef306.png" alt="location" />
</main>
</body>
</html>
The generated dist/about.html
<!DOCTYPE html>
<html>
<head>
<title>About</title>
<link href="assets/css/about.2777c101.css" rel="stylesheet">
<script src="assets/js/about.1.c5e03c0e.js" defer="defer"></script>
</head>
<body>
<main class="main-content">
<h1>Main characters</h1>
<ul>
<li class="name">Walter White, "Heisenberg"</li>
<li class="name">Jesse Pinkman</li>
</ul>
</main>
</body>
</html>
How to config splitChunks
Webpack tries to split every entry file, include template files, which completely breaks the compilation process in the plugin.
To avoid this issue, you must specify which scripts should be split, using optimization.splitChunks.cacheGroups
:
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
scripts: {
test: /\.(js|ts)$/,
chunks: 'all',
},
},
},
},
// ...
};
Note
In the
test
option must be specified all extensions of scripts which should be split.
See details by splitChunks.cacheGroups.
For example, in a template are used the scripts and styles from node_modules
:
<html>
<head>
<title>Home</title>
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="bootstrap/dist/js/bootstrap.min.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<script src="./main.js"></script>
</body>
</html>
In this use case the optimization.cacheGroups.{cacheGroup}.test
option must match exactly only JS files from node_modules
:
module.exports = {
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/].+\.(js|ts)$/, // use exactly this Regexp
name: 'vendor',
chunks: 'all',
},
},
},
},
};
Warning
Splitting CSS to many chunk is principal impossible. Splitting works only for JS files. If you use vendor styles in your style file, e.g.:
style.scss
@use "bootstrap/scss/bootstrap"; body { color: bootstrap.$primary; }
Then vendor styles will not be saved to a separate file, because
sass-loader
generates one CSS bundle code. Therefore vendor styles should be loaded in a template separately.
Warning
If you will to use the
test
as/[\\/]node_modules[\\/]
, without extension specification, then Webpack concatenates JS code together with CSS in one file, because Webpack can't differentiate CSS module from JS module, therefore you MUST match only JS files.If you want save module styles separate from your styles, then load them in a template separately:
<html> <head> <title>Home</title> <!-- load module styles separately --> <link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- load your styles separately --> <link href="./style.scss" rel="stylesheet"> </head> <body> <h1>Hello World!</h1> <script src="./main.js"></script> </body> </html>
How to split multiple node modules and save under own names
If you use many node modules and want save each module to separate file then use optimization.cacheGroups.{cacheGroup}.name
as function.
For example, many node modules are imported in the script.js
:
import { Button } from 'bootstrap';
import _, { map } from 'underscore';
// ...
Then, use the name
as following function:
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
},
plugins: [
new PugPlugin({
js: {
filename: 'js/[name].[contenthash:8].js',
},
}),
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
minSize: 10000, // extract modules bigger than 10KB, defaults is 30KB
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/].+\.(js|ts)$/, // split JS only, ignore CSS modules
// save node module under own name
name(module) {
const name = module.resourceResolveData.descriptionFileData.name.replace('@', '');
return `npm.${name}`;
},
},
},
},
},
};
The split files will be saved like this:
dist/js/npm.popperjs/core.f96a1152.js <- the `popperjs/core` used in bootstrap will be extracted too
dist/js/npm.bootstrap.f69a4e44.js
dist/js/npm.underscore.4e44f69a.js
dist/js/runtime.9cd0e0f9.js <- common runtime code
dist/js/script.3010da09.js
Also See
- ansis - The Node.js lib for ANSI color styling of text in terminal
- pug-loader The Pug loader for Webpack
- pug-plugin The Pug plugin for Webpack