Updated 2023-10-02 Usually, a junior web developer is faced with the problem of using a complex Webpack setup to create a simple HTML page with JS and CSS. First, you need to know where and how to connect source styles and scripts with HTML, and where to define the HTML template itself. You need to know what plugins and loaders are needed for this and how to configure them. The whole process isn’t simple or intuitive. Moreso, configurations happen in different places. Until now, it was necessary to use such plugins and loaders as: Package Description 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 Finally, the new HTML Bundler Plugin for Webpack has emerged, replacing this zoo of plugins and making Webpack setup incredibly simple, logical, and intuitive. What’s more? all the config happens in one place. This plugin allows you to use an HTML template as a starting point for all the dependencies used in your web application. All source styles and scripts specified in HTML are processed, and the extracted JS and CSS are saved to the output directory. Simple usage example For example, you have an HTML template containing a script, a style, and an image. You can add script and style source files 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 Webpack alias for the source images directory --> <img src="@images/logo.png"> </body> </html> The source files are processed using Webpack loaders and the plugin automatically substitutes output filenames into the generated HTML file: <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> In the Webpack config define an HTML template as the entry point in the entry option: const path = require('path'); const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); module.exports = { output: { path: path.join(__dirname, 'dist/'), }, resolve: { alias: { // aliases used in the template '@images': path.join(__dirname, 'src/images'), }, }, plugins: [ new HtmlBundlerPlugin({ // define a relative or absolute path to entry templates for // automatically processing templates in all subdirs entry: 'src/views/', // - OR - define many templates manually entry: { // output => dist/index.html index: 'src/views/home/index.html', // output => dist/pages/about.html 'pages/about': 'src/views/about/index.html', // ... }, js: { // output filename of compiled JavaScript, // used if `inline` option is false (defaults) filename: 'assets/js/[name].[contenthash:8].js', //inline: true, // inlines JS into HTML }, css: { // output filename of extracted CSS, // used if `inline` option is false (defaults) filename: 'assets/css/[name].[contenthash:8].css', //inline: true, // inlines CSS into HTML }, }), ], module: { rules: [ // styles { test: /\.(css|sass|scss)$/, use: ['css-loader', 'sass-loader'], }, // images { test: /\.(png|jpe?g|ico|svg)$/, type: 'asset/resource', generator: { filename: 'assets/img/[name].[hash:8][ext]', }, }, ], }, }; Note: No additional loaders for html templates are required, the plugin can self load templates. Using a template engine The HTML Bundler Plugin supports most popular template engines such as EJS, Eta, Handlebars, Nunjucks “out of the box“. For example, there is a Handlebars template file index.html: <html> <head> <title>{{ title }}</title> </head> <body> <h1>{{ headline }}</h1> <div> <p>{{ firstname }} {{ lastname }}</p> </div> </body> </html> To compile a Handlebars template into HTML use the preprocessor option to specify the templating engine: // ... plugins: { new HtmlBundlerPlugin({ // ... preprocessor: 'handlebars', }), }, // ... For details of the preprocessor option see here. You can pass variables into the template using the advanced syntax of the entry option and the data property: new HtmlBundlerPlugin({ entry: { index: { // => the key is the HTML output filename w/o extension import: './src/views/home/index.html', // pass data into the template (via preprocessor) data: { // ... variables as key:value }, }, }, }), The complete Webpack configuration: const path = require('path'); const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); module.exports = { output: { path: path.join(__dirname, 'dist/'), }, plugins: [ new HtmlBundlerPlugin({ entry: { index: { // import template file import: './src/views/home/index.html', data: { // pass variables into template title: 'Heisenberg', headline: 'Breaking Bad', firstname: 'Walter', lastname: 'White', }, }, }, // use the supported templating engine preprocessor: 'handlebars', }), ], // ... module rules for styles, images, fonts, etc. }; The generated HTML: <html> <head> <title>Heisenberg</title> </head> <body> <h1>Breaking Bad</h1> <div> <p>Walter White</p> </div> </body> </html> View an example used the Handlebars template in browser: Open in StackBlitz. Custom template engine You can use any templating engine via the preprocessor option as a callback function. For example, using a Mustache termplate**:** const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); const Mustache = require('mustache'); module.exports = { // ... plugins: [ new HtmlBundlerPlugin({ entry: { index: { import: './src/views/index.html', // Mustache template data: {...}, // passed data }, }, preprocessor: (template, { data }) => Mustache.render(template, data), }), ], }; Thus, you can use the latest version of any template engine, independent of whether a Webpack loader exists for it and how up-to-date it is. Often, many original loaders have not been updated for more than 2 years and may contain outdated versions of these templating engines. This means, that you can't use features (or bugfixes) of the last version of a template engine. Updated 2023-10-02 Updated 2023-10-02 Usually, a junior web developer is faced with the problem of using a complex Webpack setup to create a simple HTML page with JS and CSS. First, you need to know where and how to connect source styles and scripts with HTML, and where to define the HTML template itself. You need to know what plugins and loaders are needed for this and how to configure them. The whole process isn’t simple or intuitive. Moreso, configurations happen in different places. Until now , it was necessary to use such plugins and loaders as: Until now Package Description 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 Package Description 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 Package Description Package Package Description Description html-webpack-plugin creates HTML and inject script tag for compiled JS file into HTML html-webpack-plugin html-webpack-plugin creates HTML and inject script tag for compiled JS file into HTML creates HTML and inject script tag for compiled JS file into HTML script mini-css-extract-plugin injects link tag for processed CSS file into HTML mini-css-extract-plugin mini-css-extract-plugin injects link tag for processed CSS file into HTML injects link tag for processed CSS file into HTML link webpack-remove-empty-scripts removes generated empty JS files webpack-remove-empty-scripts webpack-remove-empty-scripts removes generated empty JS files removes generated empty JS files html-loader exports HTML html-loader html-loader exports HTML exports HTML style-loader injects an inline CSS into HTML style-loader style-loader injects an inline CSS into HTML injects an inline CSS into HTML posthtml-inline-svg injects an inline SVG icon into HTML posthtml-inline-svg posthtml-inline-svg injects an inline SVG icon into HTML injects an inline SVG icon into HTML resolve-url-loader resolves a relative URL in CSS resolve-url-loader resolve-url-loader resolves a relative URL in CSS resolves a relative URL in CSS svg-url-loader encodes a SVG data-URL as utf8 svg-url-loader svg-url-loader encodes a SVG data-URL as utf8 encodes a SVG data-URL as utf8 Finally , the new HTML Bundler Plugin for Webpack has emerged, replacing this zoo of plugins and making Webpack setup incredibly simple, logical, and intuitive. What’s more? all the config happens in one place. Finally HTML Bundler Plugin This plugin allows you to use an HTML template as a starting point for all the dependencies used in your web application. All source styles and scripts specified in HTML are processed, and the extracted JS and CSS are saved to the output directory. Simple usage example For example, you have an HTML template containing a script, a style, and an image. You can add script and style source files 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 Webpack alias for the source images directory --> <img src="@images/logo.png"> </body> </html> <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 Webpack alias for the source images directory --> <img src="@images/logo.png"> </body> </html> The source files are processed using Webpack loaders and the plugin automatically substitutes output filenames into the generated HTML file: <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> <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> In the Webpack config define an HTML template as the entry point in the entry option: entry const path = require('path'); const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); module.exports = { output: { path: path.join(__dirname, 'dist/'), }, resolve: { alias: { // aliases used in the template '@images': path.join(__dirname, 'src/images'), }, }, plugins: [ new HtmlBundlerPlugin({ // define a relative or absolute path to entry templates for // automatically processing templates in all subdirs entry: 'src/views/', // - OR - define many templates manually entry: { // output => dist/index.html index: 'src/views/home/index.html', // output => dist/pages/about.html 'pages/about': 'src/views/about/index.html', // ... }, js: { // output filename of compiled JavaScript, // used if `inline` option is false (defaults) filename: 'assets/js/[name].[contenthash:8].js', //inline: true, // inlines JS into HTML }, css: { // output filename of extracted CSS, // used if `inline` option is false (defaults) filename: 'assets/css/[name].[contenthash:8].css', //inline: true, // inlines CSS into HTML }, }), ], module: { rules: [ // styles { test: /\.(css|sass|scss)$/, use: ['css-loader', 'sass-loader'], }, // images { test: /\.(png|jpe?g|ico|svg)$/, type: 'asset/resource', generator: { filename: 'assets/img/[name].[hash:8][ext]', }, }, ], }, }; const path = require('path'); const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); module.exports = { output: { path: path.join(__dirname, 'dist/'), }, resolve: { alias: { // aliases used in the template '@images': path.join(__dirname, 'src/images'), }, }, plugins: [ new HtmlBundlerPlugin({ // define a relative or absolute path to entry templates for // automatically processing templates in all subdirs entry: 'src/views/', // - OR - define many templates manually entry: { // output => dist/index.html index: 'src/views/home/index.html', // output => dist/pages/about.html 'pages/about': 'src/views/about/index.html', // ... }, js: { // output filename of compiled JavaScript, // used if `inline` option is false (defaults) filename: 'assets/js/[name].[contenthash:8].js', //inline: true, // inlines JS into HTML }, css: { // output filename of extracted CSS, // used if `inline` option is false (defaults) filename: 'assets/css/[name].[contenthash:8].css', //inline: true, // inlines CSS into HTML }, }), ], module: { rules: [ // styles { test: /\.(css|sass|scss)$/, use: ['css-loader', 'sass-loader'], }, // images { test: /\.(png|jpe?g|ico|svg)$/, type: 'asset/resource', generator: { filename: 'assets/img/[name].[hash:8][ext]', }, }, ], }, }; Note: No additional loaders for html templates are required, the plugin can self load templates. Note: Using a template engine The HTML Bundler Plugin supports most popular template engines such as EJS , Eta , Handlebars , Nunjucks “ out of the box “. EJS Eta Handlebars Nunjucks out of the box For example, there is a Handlebars template file index.html : index.html <html> <head> <title>{{ title }}</title> </head> <body> <h1>{{ headline }}</h1> <div> <p>{{ firstname }} {{ lastname }}</p> </div> </body> </html> <html> <head> <title>{{ title }}</title> </head> <body> <h1>{{ headline }}</h1> <div> <p>{{ firstname }} {{ lastname }}</p> </div> </body> </html> To compile a Handlebars template into HTML use the preprocessor option to specify the templating engine : preprocessor templating engine // ... plugins: { new HtmlBundlerPlugin({ // ... preprocessor: 'handlebars', }), }, // ... // ... plugins: { new HtmlBundlerPlugin({ // ... preprocessor: 'handlebars', }), }, // ... For details of the preprocessor option see here . preprocessor here You can pass variables into the template using the advanced syntax of the entry option and the data property: advanced syntax entry data new HtmlBundlerPlugin({ entry: { index: { // => the key is the HTML output filename w/o extension import: './src/views/home/index.html', // pass data into the template (via preprocessor) data: { // ... variables as key:value }, }, }, }), new HtmlBundlerPlugin({ entry: { index: { // => the key is the HTML output filename w/o extension import: './src/views/home/index.html', // pass data into the template (via preprocessor) data: { // ... variables as key:value }, }, }, }), The complete Webpack configuration: const path = require('path'); const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); module.exports = { output: { path: path.join(__dirname, 'dist/'), }, plugins: [ new HtmlBundlerPlugin({ entry: { index: { // import template file import: './src/views/home/index.html', data: { // pass variables into template title: 'Heisenberg', headline: 'Breaking Bad', firstname: 'Walter', lastname: 'White', }, }, }, // use the supported templating engine preprocessor: 'handlebars', }), ], // ... module rules for styles, images, fonts, etc. }; const path = require('path'); const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); module.exports = { output: { path: path.join(__dirname, 'dist/'), }, plugins: [ new HtmlBundlerPlugin({ entry: { index: { // import template file import: './src/views/home/index.html', data: { // pass variables into template title: 'Heisenberg', headline: 'Breaking Bad', firstname: 'Walter', lastname: 'White', }, }, }, // use the supported templating engine preprocessor: 'handlebars', }), ], // ... module rules for styles, images, fonts, etc. }; The generated HTML: <html> <head> <title>Heisenberg</title> </head> <body> <h1>Breaking Bad</h1> <div> <p>Walter White</p> </div> </body> </html> <html> <head> <title>Heisenberg</title> </head> <body> <h1>Breaking Bad</h1> <div> <p>Walter White</p> </div> </body> </html> View an example used the Handlebars template in browser: Open in StackBlitz . Open in StackBlitz Custom template engine Custom template engine You can use any templating engine via the preprocessor option as a callback function . preprocessor callback function For example, using a Mustache termplate**:** Mustache const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); const Mustache = require('mustache'); module.exports = { // ... plugins: [ new HtmlBundlerPlugin({ entry: { index: { import: './src/views/index.html', // Mustache template data: {...}, // passed data }, }, preprocessor: (template, { data }) => Mustache.render(template, data), }), ], }; const HtmlBundlerPlugin = require('html-bundler-webpack-plugin'); const Mustache = require('mustache'); module.exports = { // ... plugins: [ new HtmlBundlerPlugin({ entry: { index: { import: './src/views/index.html', // Mustache template data: {...}, // passed data }, }, preprocessor: (template, { data }) => Mustache.render(template, data), }), ], }; Thus, you can use the latest version of any template engine, independent of whether a Webpack loader exists for it and how up-to-date it is. Often, many original loaders have not been updated for more than 2 years and may contain outdated versions of these templating engines. This means, that you can't use features (or bugfixes) of the last version of a template engine.