3.11.0 • Published 10 days ago

html-bundler-webpack-plugin v3.11.0

Weekly downloads
-
License
ISC
Repository
github
Last release
10 days ago

npm node node Test codecov node

HTML template as entry point

The HTML Bundler generates static HTML or template function from various templates containing source files of scripts, styles, images, fonts and other resources, similar to how it works in Vite. This plugin allows using a template file as an entry point.

A template imported in JS will be compiled into template function. You can use the template function in JS to render the template with variables in runtime on the client-side in the browser.

This plugin is an advanced successor to html-webpack-plugin and a replacement of the plugins and loaders.

📢 Please help promote this plugin on social networks so that developers know about this useful plugin.\ Special Thanks to Andrew Lisowski for the tooltips in the video www.youtube.com/@devtoolsfm.

For example, using source asset files is HTML template ./src/views/index.html:

<html>
  <head>
    <!-- relative path to SCSS source file -->
    <link href="../scss/style.scss" rel="stylesheet" />
    <!-- relative path to TypeScript source file -->
    <script src="../app/main.ts" defer="defer"></script>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- relative path to image source file -->
    <img src="../assets/images/picture1.png" />
    <!-- Webpack alias as path (src/assets/images/) to image source file -->
    <img src="@images/picture2.png" />
  </body>
</html>

Open an example in StackBlitz

The folder structure of the example:

./src/views/index.html
./src/app/main.ts
./src/scss/style.scss
./src/assets/images/picture1.png
./src/assets/images/picture2.png

All source file paths in dependencies will be resolved and auto-replaced with correct URLs in the bundled output. The resolved assets will be processed via Webpack plugins/loaders and placed into the output directory. You can use a relative path or Webpack alias to a source file.


📋 Table of Contents

🚀 Install and Quick Start

🖼 Usage examples


🦖 Mozilla already uses this plugin to build static HTML files for the Mozilla AI GUIDE site.

The plugin has been actively developed for more than 2 years, and since 2023 it is open source.\

Please support this project by giving it a star ⭐.

💡 Highlights

  • An entry point is any HTML template.
  • Auto processing multiple HTML templates in the entry path.
  • Allows to specify script and style source files directly in HTML:
    • <link href="./style.scss" rel="stylesheet">
    • <script src="./app.tsx" defer="defer"></script>
  • Resolves source files in default attributes href src srcset etc. using relative path or alias:
    • <link href="../images/favicon.svg" type="image/svg" rel=icon />
    • <img src="@images/pic.png" srcset="@images/pic400.png 1x, @images/pic800.png 2x" />
  • Inlines JS and CSS into HTML.
  • Inlines images into HTML and CSS.
  • Supports styles used in *.vue files.
  • Renders the template engines such as Eta, EJS, Handlebars, Nunjucks, Pug, TwigJS, LiquidJS.
  • Compile a template into template function for usage in JS on the client-side.
  • Generates the preload tags for fonts, images, video, scripts, styles, etc.
  • Generates the integrity attribute in the link and script tags.
  • Generates the favicons of different sizes for various platforms.
  • You can create own plugin using the Plugin Hooks.
  • Over 500 tests.

See the full list of features.

❤️ Sponsors & Patrons

Thank you to all our sponsors and patrons!

⚙️ How works the plugin

The plugin resolves references in the HTML template and adds them to the Webpack compilation. Webpack will automatically process the source files, and the plugin replaces the references with their output filenames in the generated HTML. See how the plugin works under the hood.

✅ Profit

  • Simplify Webpack config using one powerful plugin instead of many different plugins and loaders.

  • Start from HTML, not from JS. Define an HTML template file as an entry point.

  • Specify script and style source files directly in an HTML template, and you no longer need to define them in Webpack entry or import styles in JavaScript.

  • Use any template engine without additional plugins and loaders. Most popular template engines supported "out of the box".

❓Question / Feature Request / Bug

If you have discovered a bug or have a feature suggestion, feel free to create an issue on GitHub.

📚 Read it

🔆 What's New in v3

  • NEW added supports the template function in JS runtime on the client-side.
  • NEW added Pug preprocessor.
  • NEW added Twig preprocessor.
  • NEW added supports the dynamic import of styles.
  • NEW added supports the CSS Modules for styles imported in JS.
  • NEW added CSS extraction from styles used in *.vue files.
  • NEW added Hooks & Callbacks. Now you can create own plugin to extend this plugin.
  • NEW added the build-in FaviconsBundlerPlugin to generate and inject favicon tags.

🔆 What's New in v2

  • NEW added importing style files in JavaScript.
  • NEW added support the integrity.
  • NEW you can add/delete/rename a template file in the entry path without restarting Webpack

For full release notes see the changelog.

⚠️ Limitations

Cache type

The current version works stable with cache.type as 'memory' (Webpack's default setting).\ Support for the 'filesystem' cache type is experimental.

Multiple config files

The multiple config files are not supported, because in some special use cases the Webpack API works not properly (all previous configurations are overridden by the latest configuration).

Instead of this:

npx webpack -c app1.config.js app2.config.js

you can use following:

npx webpack -c app1.config.js
npx webpack -c app2.config.js

Install and Quick start

Install the html-bundler-webpack-plugin:

npm install html-bundler-webpack-plugin --save-dev

It's recommended to combine html-bundler-webpack-plugin with the css-loader and the sass-loader.\ Install additional packages for styles:

npm install css-loader sass-loader sass --save-dev

Start with an HTML template. Add the <link> and <script> tags. You can include asset source files such as SCSS, JS, images, and other media files directly in an HTML template.

The plugin resolves <script src="..."> <link href="..."> and <img src="..." srcset="..."> that references your script, style and image source files.

For example, there is the template ./src/views/home.html:

<html>
  <head>
    <!-- variable from Webpack config -->
    <title><%= title %></title>
    <!-- relative path to favicon source file -->
    <link href="./favicon.ico" rel="icon" />
    <!-- relative path to SCSS source file -->
    <link href="./style.scss" rel="stylesheet" />
    <!-- relative path to JS source file -->
    <script src="./main.js" defer="defer"></script>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- relative path to image source file -->
    <img src="./picture.png" />
  </body>
</html>

All source filenames should be relative to the entrypoint template, or you can use Webpack alias. The references are rewritten in the generated HTML so that they link to the correct output files.

The generated HTML contains URLs of the output filenames:

<html>
  <head>
    <title>Homepage</title>
    <link href="img/favicon.3bd858b4.ico" rel="icon" />
    <link href="css/style.05e4dd86.css" rel="stylesheet" />
    <script src="js/main.f4b855d8.js" defer="defer"></script>
  </head>
  <body>
    <h1>Hello World!</h1>
    <img src="img/picture.58b43bd8.png" />
  </body>
</html>

Pages can be defined in the entry option. JS and CSS can be configured using the js and css options.

If the entry option is a path, the plugin finds all templates automatically and keep the same directory structure in the output directory.

If the entry option is an object, the key is an output filename without .html extension and the value is a template file.

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlBundlerPlugin({
      // automatically processing all templates in the path
      entry: 'src/views/',
      
      // - OR - define pages manually (key is output filename w/o `.html`)
      entry: {
        index: 'src/views/home.html', // => dist/index.html
        'news/sport': 'src/views/news/sport/index.html', // => dist/news/sport.html
      },
      
      // - OR - define pages with variables
      entry: [
        {
          import: 'src/views/home.html', // template file
          filename: 'index.html', // => dist/index.html
          data: { title: 'Homepage' }, // pass variables into template
        },
        {
          import: 'src/views/news/sport/index.html', // template file
          filename: 'news/sport.html', // => dist/news/sport.html
          data: { title: 'Sport news' }, // pass variables into template
        },
      ],
      
      // - OR - combine both the pages with and w/o variables in one entry
      entry: {
        // simple page config w/o variables
        index: 'src/views/home.html', // => dist/index.html
        // advanced page config with variables
        'news/sport': { // => dist/news/sport.html
          import: 'src/views/home.html', // template file
          data: { title: 'Sport news' }, // pass variables into template
        },
      },

      js: {
        // JS output filename, used if `inline` option is false (defaults)
        filename: 'js/[name].[contenthash:8].js',
        //inline: true, // inlines JS into HTML
      },
      css: {
        // CSS output filename, used if `inline` option is false (defaults)
        filename: 'css/[name].[contenthash:8].css',
        //inline: true, // inlines CSS into HTML
      },
    }),
  ],
  module: {
    rules: [
      {
        test: /\.(css|sass|scss)$/,
        use: ['css-loader', 'sass-loader'],
      },
      {
        test: /\.(ico|png|jp?g|webp|svg)$/,
        type: 'asset/resource',
        generator: {
          filename: 'img/[name].[hash:8][ext][query]',
        },
      },
    ],
  },
};

Note

To define the JS output filename, use the js.filename option of the plugin.\ Don't use Webpack's output.filename, hold all relevant settings in one place - in plugin options.\ Both places have the same effect, but js.filename has priority over output.filename.

No additional template loader is required. The plugin handels templates with base EJS-like syntax automatically. The default templating engine is Eta.

For using the native EJS syntax see Templating with EJS.\ For using the Handlebars see Templating with Handlebars.\ For other templates see Template engines.

For custom templates, you can use the preprocessor option to handels any template engine.

Open in StackBlitz

Open in StackBlitz

See boilerplate


Table of Contents

  1. Features
  2. Install and Quick start
  3. Webpack options
  4. Build-in Plugins
  5. Third-party Plugins
  6. Hooks & Callbacks
  7. Plugin options
  8. Loader options
  9. Using template engines
  10. Using template in JavaScript
  11. Setup Live Reload
  12. Recipes
  13. Problems & Solutions
  14. Demo sites
  15. Usage examples

Features

(*) - asset/source works currently for SVG only, in a next version will work for other files too

Why do many developers switch from Webpack to other bundlers?

One of the reasons they cite is the complex configuration many different plugins and loaders for one simple thing - rendering an HTML page with assets.

The HTML bundler plugin "changes the rule of the game", making configuration very simple and clear. Just one plugin replaces the functionality of the plugins and loaders:

PackageFeatures
html-webpack-plugincreates HTML and inject script tag for compiled JS file into HTML
mini-css-extract-plugininjects link tag for processed CSS file into HTML
webpack-remove-empty-scriptsremoves generated empty JS files
html-loaderexports HTML, resolving attributes
style-loaderinjects an inline CSS into HTML
html-webpack-inject-preloadinject preload link tags
preload-webpack-plugininject preload link tags
html-webpack-inline-source-plugininline JS and CSS into HTML
html-inline-css-webpack-plugininline CSS into HTML
posthtml-inline-svginjects an inline SVG icon into HTML
resolve-url-loaderresolves a relative URL in CSS
svg-url-loaderencodes a SVG data-URL as utf8
webpack-subresource-integrity enables Subresource Integrity
favicons-webpack-plugin generates favicons and icons
handlebars-webpack-pluginrenders Handlebars templates
handlebars-loadercompiles Handlebars templates
pug-loadercompiles Pug templates
nunjucks-loadercompiles Nunjucks templates

↑ back to contents

Webpack options

Important Webpack options used to properly configure this plugin.

output.path

Type: string Default: path.join(process.cwd(), 'dist')

The root output directory for all processed files, as an absolute path.\ You can omit this option, then all generated files will be saved under dist/ in your project directory.

output.publicPath

Type: string|function Default: auto

The value of the option is prefixed to every URL created by this plugin. If the value is not the empty string or auto, then the option must end with /.

The possible values:

  • publicPath: 'auto' - automatically determines a path of an asset relative of their issuer. The generated HTML page can be opened directly form the local directory and all js, css and images will be loaded in a browser.
  • publicPath: '' - a path relative to an HTML page, in the same directory. The resulting path is different from a path generated with auto.
  • publicPath: '/' - a path relative to document root directory on a server
  • publicPath: '/assets/' - a sub path relative to document root directory on a server
  • publicPath: '//cdn.example.com/' - an external URL with the same protocol (http:// or https://)
  • publicPath: 'https://cdn.example.com/' - an external URL with the https:// protocol only

output.filename

Type: string|function Default: [name].js

The output name of a generated JS file.\ Highly recommended to define the filename in the Plugin option js.filename.

The output name of a generated CSS file is determined in the Plugin option css.filename.

Define output JS and CSS filenames in the Plugin option, in one place:

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
  plugins: [
    new HtmlBundlerPlugin({
      js: {
        // define the output name of a generated JS file here
        filename: 'js/[name].[contenthash:8].js',
      },
      css: {
        // define the output name of a generated CSS file here
        filename: 'css/[name].[contenthash:8].css',
      },
    }),
  ],
};

entry

The starting point to build the bundle.

Note

Using this plugin an entry point is an HTML template. All script and style source files must be specified in the HTML template.

You can use the Webpack entry option to define HTML templates, but it is highly recommended to define all templates in plugin option entry, because it has an additional data property (not available in the Webpack entry) to pass custom variables into the HTML template.

For details see the plugin option entry.

↑ back to contents

Build-in Plugins

There are the most useful plugins available "out of the box". The build-in plugins maintained by the HtmlBundlerPlugin.

All build-in plugins are in the /plugins subdirectory of the HtmlBundlerPlugin.

FaviconsBundlerPlugin

The FaviconsBundlerPlugin generates favicons for different devices and injects favicon tags into HTML head.

Install

This plugin requires the additional favicons package.

npm install favicons -D

Config

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const { FaviconsBundlerPlugin } = require('html-bundler-webpack-plugin/plugins');

module.exports = {
  plugins: [
    new HtmlBundlerPlugin({
      entry: {
        // source favicon file must be specified directly in HTML using link tag
        index: './src/views/index.html',
      },
    }),
    // add the favicons plugin
    new FaviconsBundlerPlugin({
      enabled: 'auto', // true, false, auto - generate favicons in production mode only
      // favicons configuration options, see https://github.com/itgalaxy/favicons#usage
      faviconOptions: {
        path: '/img/favicons', // favicons output path relative to webpack output.path
        icons: {
          android: true, // Create Android homescreen icon.
          appleIcon: true, // Create Apple touch icons.
          appleStartup: false, // Create Apple startup images.
          favicons: true, // Create regular favicons.
          windows: false, // Create Windows 8 tile icons.
          yandex: false, // Create Yandex browser icon.
        },
      },
    }),
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|ico|svg)$/,
        type: 'asset/resource',
      },
    ],
  },
};

FaviconsBundlerPlugin options

  • enabled: boolean | 'auto'\ if is 'auto' then generate favicons in production mode only, in development mode will be used original favicon processed via webpack asset module.
  • faviconOptions: FaviconOptions - options of the favicons module. See configuration options.

Usage

The source file of your favicon must be specified directly in HTML as the link tag with rel="icon" attribute.

If the FaviconsBundlerPlugin is disabled or as auto in development mode, then the source favicon file will be processed via webpack.

If the FaviconsBundlerPlugin is enabled or as auto in production mode, then the source favicon file will be processed via favicons module and the original link tag with favicon will be replaced with generated favicon tags.

For example, there is the src/views/index.html

<!DOCTYPE html>
<html>
<head>
  <!-- source favicon file relative to this HTML file, or use a webpack alias -->
  <link href="./myFavicon.png" rel="icon" />
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

The generated HTML when FaviconsBundlerPlugin is disabled:

<!DOCTYPE html>
<html>
<head>
  <!-- output favicon file -->
  <link href="assets/img/myFavicon.1234abcd.png" rel="icon" />
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

The generated HTML when FaviconsBundlerPlugin is enabled:

<!DOCTYPE html>
<html>
<head>
  <!-- original tag is replaced with tags generated by favicons module -->
  <link rel="apple-touch-icon" sizes="1024x1024" href="/img/favicons/apple-touch-icon-1024x1024.png">
  <link rel="apple-touch-icon" sizes="114x114" href="/img/favicons/apple-touch-icon-114x114.png">
  <link rel="apple-touch-icon" sizes="120x120" href="/img/favicons/apple-touch-icon-120x120.png">
  <link rel="apple-touch-icon" sizes="144x144" href="/img/favicons/apple-touch-icon-144x144.png">
  <link rel="apple-touch-icon" sizes="152x152" href="/img/favicons/apple-touch-icon-152x152.png">
  <link rel="apple-touch-icon" sizes="167x167" href="/img/favicons/apple-touch-icon-167x167.png">
  <link rel="apple-touch-icon" sizes="180x180" href="/img/favicons/apple-touch-icon-180x180.png">
  <link rel="apple-touch-icon" sizes="57x57" href="/img/favicons/apple-touch-icon-57x57.png">
  <link rel="apple-touch-icon" sizes="60x60" href="/img/favicons/apple-touch-icon-60x60.png">
  <link rel="apple-touch-icon" sizes="72x72" href="/img/favicons/apple-touch-icon-72x72.png">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/favicons/apple-touch-icon-76x76.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/img/favicons/favicon-16x16.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/img/favicons/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="48x48" href="/img/favicons/favicon-48x48.png">
  <link rel="icon" type="image/x-icon" href="/img/favicons/favicon.ico">
  <link rel="manifest" href="/img/favicons/manifest.webmanifest">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <meta name="apple-mobile-web-app-title" content="My App">
  <meta name="application-name" content="My App">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="theme-color" content="#fff">
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

↑ back to contents

Third-party Plugins

The third-party plugins not maintained by the HtmlBundlerPlugin. It potentially does not have the same support, security policy or license as Build-in Plugins.

You can create own plugin using the plugin hooks. As a reference plugin, you can use the FaviconsBundlerPlugin.

If you have a useful plugin, create a PR with the link to you plugin.

The plugin name must end with -bundler-plugin, e.g. hello-world-bundler-plugin.

Currently there are no plugins yet. Be the first to create one.

↑ back to contents

Hooks & Callbacks

Using hooks and callbacks, you can extend the functionality of this plugin.

The hook can be defined in an external plugin. The callback is defined as an option in the HTMLBundlerPlugin.

Most hooks have a callback with the same name. Each callback is called after hook with the same name. So with a callback, you can change the result of the hook.

When using callbacks

If you have small code just for your project or are doing debugging, you can use callbacks.

When using hooks

Using hooks you can create your own plugin.

How the plugin works under the hood.

How to use hooks

The simplest way, add the { apply() { ... } } object to the array of the Webpack plugins:

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlBundlerPlugin({
      entry: {
        index: './src/index.html',
      },
    }),
    // your plugin
    {
      apply(compiler) {
        const pluginName = 'MyPlugin';

        compiler.hooks.compilation.tap(pluginName, (compilation) => {
          const hooks = HtmlBundlerPlugin.getHooks(compilation);

          // modify generated HTML of the index.html template
          hooks.beforeEmit.tap(pluginName, (content, { name, sourceFile, assetFile }) => {
            return content.replace('something...', 'other...')
          });
        });
      },
    },
  ],
};

You can use this template as the basis for your own plugin:

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

class MyPlugin {
  pluginName = 'my-plugin';
  options = {};

  /**
   * @param {{ enabled: boolean | 'auto'}} options The options of your plugin.
   */
  constructor(options = {}) {
    this.options = options;
  }

  apply(compiler) {
    // you can use the API of the HtmlBundlerPlugin.option
    const enabled = HtmlBundlerPlugin.option.toBool(this.options?.enabled, true, 'auto');
    const outputPath = HtmlBundlerPlugin.option.getWebpackOutputPath();

    if (!enabled) {
      return;
    }

    const { pluginName } = this;
    const { webpack } = compiler; // instance of the Webpack
    const fs = compiler.inputFileSystem.fileSystem; // instance of the Webpack FyleSystem

    // start your plugin from the webpack compilation hook
    compiler.hooks.compilation.tap(pluginName, (compilation) => {
      const hooks = HtmlBundlerPlugin.getHooks(compilation);
      
      // usage of the sync, async and promise hooks

      // sync hook
      hooks.<hookName>.tap(pluginName, (...arguments) => {
        // do somthing here ...
        const result = 'your result';
        // return the result
        return result;
      });
      
      // async hook
      hooks.<hookName>.tapAsync(pluginName, (...arguments, callback) => {
        // do somthing here ...
        const result = 'your result';
        // call the callback function to resolve the async hook
        callback(result);
      });
      
      // promise hook
      hooks.<hookName>.tapPromise(pluginName, (...arguments) => {
        // do somthing here ...
        const result = 'your result';
        // return the promise with the result
        return Promise.resolve(result);
      });
    });
  }
}

module.exports = MyPlugin;

Then add your plugin in the webpack config:

const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const MyBundlerPlugin = require('my-bundler-plugin');

module.exports = {
  plugins: [
    new HtmlBundlerPlugin({
      entry: {
        index: './src/index.html',
      },
    }),
    // your plugin
    new MyBundlerPlugin({ enabled: true });
  ],
};

For an example implementation see FaviconsBundlerPlugin.

↑ back to contents

beforePreprocessor

AsyncSeriesWaterfallHook<[
  content: string,
  loaderContext: LoaderContext<Object> & { data: { [key: string]: any } | string }
]>;

For details on AsyncSeriesWaterfallHook see the hook interface.

For details on hook parameters, see in the beforePreprocessor callback option.

↑ back to contents

preprocessor

AsyncSeriesWaterfallHook<[
  content: string,
  loaderContext: LoaderContext<Object> & { data: { [key: string]: any } | string }
]>;

For details on AsyncSeriesWaterfallHook see the hook interface.

For details on hook parameters, see in the preprocessor callback option.

↑ back to contents

resolveSource

SyncWaterfallHook<[
  source: string,
  info: {
    type: 'style' | 'script' | 'asset';
    tag: string;
    attribute: string;
    value: string;
    resolvedFile: string;
    issuer: string
  },
]>;

no calback

Called after resolving of a source attribute defined by source loader option.

For details on SyncWaterfallHook see the hook interface.

Hook parameters:

  • source - a source of the tag where are parsed attributes, e.g. <link href="./favicon.png" rel="icon">
  • info - an object with parsed information:
    • type - the type of the tag
    • tag - the tag name, e.g. 'link', 'script', 'img', etc.
    • attribute - the attribute name, e.g. 'src', 'href', etc.
    • value - the attribute value
    • resolvedFile - the resolved file from the value
    • issuer - the template file

Return a string to override the resolved value of the attribute or undefined to keep the resolved value.

↑ back to contents

postprocess

AsyncSeriesWaterfallHook<[content: string, info: TemplateInfo]>;

For details on AsyncSeriesWaterfallHook see the hook interface.

For details on hook parameters, see in the postprocess callback option.

↑ back to contents

beforeEmit

AsyncSeriesWaterfallHook<[content: string, entry: CompileEntry]>;

For details on AsyncSeriesWaterfallHook see the hook interface.

For details on hook parameters, see in the beforeEmit callback option.

↑ back to contents

afterEmit

AsyncSeriesHook<[entries: CompileEntries]>;

For details on AsyncSeriesHook see the hook interface.

For details on hook parameters, see in the afterEmit callback option.

↑ back to contents

integrityHashes

AsyncSeriesHook<{
  // the map of the output asset filename to its integrity hash
  hashes: Map<string, string>;
}>;

Called after all assets have been processed and hashes have finite values and cannot be changed, at the afterEmit stage. This can be used to retrieve the integrity values for the asset files.

For details on AsyncSeriesHook see the hook interface.

Callback Parameter: hashes is the map of the output asset filename to its integrity hash. The map only contains JS and CSS assets that have a hash.

You can write your own plugin, for example, to extract integrity values into the separate file:

const fs = require('fs');
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
  output: {
    crossOriginLoading: 'anonymous', // required for Subresource Integrity
  },
  plugins: [
    new HtmlBundlerPlugin({
      entry: {
        index: './src/index.html',
      },
      js: {
        filename: '[name].[contenthash:8].js',
        chunkFilename: '[name].[contenthash:8].chunk.js',
      },
      css: {
        filename: '[name].[contenthash:8].css',
        chunkFilename: '[name].[contenthash:8].chunk.css',
      },
      integrity: 'auto',
    }),
    // your plugin to extract the integrity values
    {
      apply(compiler) {
        compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
          const hooks = HtmlBundlerPlugin.getHooks(compilation);
          hooks.integrityHashes.tapAsync(
            'MyPlugin', 
            (hashes) => Promise.resolve().then(() => {
                if (hashes.size > 0) {
                  const saveAs = path.join(__dirname, 'dist/integrity.json');
                  const json = Object.fromEntries(hashes);
                  fs.writeFileSync(saveAs, JSON.stringify(json, null, '  ')); // => save to file
                  console.log(hashes); // => output to console
                }
              })
            );
          }
        );
      },
    },
  ],
};

The content of the dist/integrity.json file looks like:

{
  "815.49b3d882.chunk.js": "sha384-dBK6nNrKKk2KjQLYmHZu6tuWwp7kBzzEvdX+4Ni11UzxO2VHvP4A22E/+mmeduul",
  "main.9c043cce.js": "sha384-AbfLh7mk6gCp0nhkXlAnOIzaHeJSB8fcV1/wT/FWBHIDV7Blg9A0sukZ4nS3xjtR"
  "main.dc4ea4af.chunk.css": "sha384-W/pO0vwqqWBj4lq8nfe+kjrP8Z78smCBttkCvx1SYKrVI4WEdJa6W6i0I2hoc1t7",
  "style.47f4da55.css": "sha384-gaDmgJjLpipN1Jmuc98geFnDjVqWn1fixlG0Ab90qFyUIJ4ARXlKBsMGumxTSu7E",
}

↑ back to contents

Plugin options

test

Type: RegExp Default: /\.(html|eta)$/

The test option allows to handel only those templates as entry points that match the name of the source file.

For example, if you have other templates, e.g. *.liquid, as entry points, then you can set the option to match custom template files: test: /\.(html|liquid)$/.

The test value is used in the default loader.

Note

Using the preprocessor options will be added the templating engine extensions in the test automatically. Defaults preprocessor is Eta therefore is used the /\.(html|eta)$/ RegExp.

For example, if you define the preprocessor option as the handlebars, then will be used the /\.(html|hbs|handlebars)$/ RegExp automatically.

Why is it necessary to define it? Can't it be automatically processed?

This plugin is very powerful and has many experimental features not yet documented. One of the next features will be the processing scripts and styles as entry points for library bundles without templates. To do this, the plugin must differentiate between a template entry point and a script/style entry point. This plugin can completely replace the functionality of mini-css-extract-plugin and webpack-remove-empty-scripts in future.

entry

Type: EntryObject | Array<EntryDescription> | string.

The EntryObject is identical to Webpack entry plus additional data property to pass custom variables into the HTML template.

Specify template files as entry points in the entry option.

An HTML template is a starting point for collecting all the dependencies used in your web application. Specify source scripts (JS, TS) and styles (CSS, SCSS, LESS, etc.) directly in HTML. The plugin automatically extracts JS and CSS whose source files are specified in an HTML template.

type EntryObject = {
  [name: string]: EntryDescription | string;
};

The key of the EntryObject is the output filename without an extension, relative to the outputPath option.

Simple syntax

When the entry point value is a string, it must be an absolute or relative template file. For example:

{
  entry: {
    index: path.join(__dirname, 'src/views/home/index.html'), // => dist/index.html
    'news/sport': 'src/views/news/sport/index.html', // => dist/news/sport.html
  },
}

Advanced syntax

If you need to pass data to a template or want to dynamically generate an output filename regardless of the entry key, you can define the value of an entry as an EntryDescription object.

type EntryDescription = {
  /**
   * Template file, relative of context or absolute.
   */
  import: string;
  /**
   * Specifies the filename of the output file.
   */
  filename?: FilenameTemplate;
  /**
   * The template data.
   */
  data?: { [key: string]: any } | string;
};

type FilenameTemplate =
  | string
  | ((pathData: import('webpack/Compilation').PathData, assetInfo?: import('webpack/Compilation').AssetInfo) => string);
import

The import is a path to a template file, absolute or relative to the Webpack context option.

filename

When the filename is defined as a string, it will be used as the output html filename. In this case, the entry key can be any unique string.

For example:

{
  entry: {
    page01: {
      import: 'src/views/news/sport/index.html', // <= source template
      filename: 'news/sport.html', // => output ./dist/news/sport.html
    },
  },
}

When the filename is defined as a template string, then the entry key will be used as the [name] in the template string. Defaults, the filename is the [name].html template string.

For example:

{
  entry: {
    'news/sport': {
      import: 'src/views/news/sport/index.html', // <= source template
      filename: '[name].html', // => output ./dist/news/sport.html
    },
  },
}

The example above is equivalent to the simple syntax:

{
  entry: {
    'news/sport': 'src/views/news/sport/index.html',
  },
}

data

The data is passed into preprocessor to render the template with variables.

When the data is an object, it will be loaded once with Webpack start. After changing the data, you need to restart Webpack.

For example:

{
  entry: {
    index: {
      import: 'src/views/index.html',
      // pass data as an object
      data: {
        title: 'Home',
      }
    },
}

When the data is a string, it must be an absolute or relative path to a file. The file can be a JSON file or a JS file that exports the data as an object. Use the data as a file if you want to get dynamic data in a template. The data file will be reloaded after changes, without restarting Webpack.

For example:

{
  entry: {
    index: {
      import: 'src/views/index.html',
      // load data from JSON file
      data: 'src/data/home.json',
    },
  },
}

The data file src/data/home.json:

{
  "title": "Home"
}

To pass global variables in all templates use the data loader option.

Note

You can define templates both in Webpack entry and in the entry option of the plugin. The syntax is identical. But the data property can only be used in the entry option of the plugin.

Entry as an array

If the entry is the array of the EntryDescription then the filename property is required.

{
  entry: [
    {
      filename: 'index.html', // => output filename dist/index.html
      import: 'src/views/index.html', // template file
      data: { title: 'Homepage' }, // page specifically variables
    },
    {
      filename: 'about.html',
      import: 'src/views/about.html',
      data: { title: 'About' },
    },
    {
      filename: 'news/sport.html',
      import: 'src/views/news/sport.html',
      data: { title: 'Sport' },
    },
  ],
}

Entry as an object

The absolute equivalent to the example above using an object is:

{
  entry: {
    index: { // => output filename dist/index.html
      import: 'src/views/index.html', // template file
      data: { title: 'Homepage' }, // page specifically variables
    },
    about: {
      import: 'src/views/about.html',
      data: { title: 'About' },
    },
    'news/sport': {
      import: 'src/views/news/sport.html',
      data: { title: 'Sport' },
    },
  },
}

The difference between object and array notation:

  • Using the object notation the output filename is the key of the entry item without the .html file extension.
  • Using the array notation the output filename is the filename property of the array item contained the file with .html file extension.

Entry as a path to templates

You can define the entry as a path to recursively detect all templates from that directory.

When the value of the entry is a string, it must be an absolute or relative path to the templates' directory. Templates matching the test option are detected recursively from the path. The output files will have the same folder structure as source template directory.

For example, there are files in the template directory ./src/views/

./src/views/index.html
./src/views/about/index.html
./src/views/news/sport/index.html
./src/views/news/sport/script.js
./src/views/news/sport/style.scss
...

Define the entry option as the relative path to pages:

new HtmlBundlerPlugin({
  entry: 'src/views/',
});

Files that are not matching to the test option are ignored. The output HTML filenames keep their source structure in the output directory relative to the entry path:

./dist/index.html
./dist/about/index.html
./dist/news/sport/index.html
...

If you need to modify the output HTML filename, use the filename option as the function.

For example, we want keep a source structure for all pages, while ./src/views/home/index.html should not be saved as ./dist/home/index.htm, but as ./dist/index.htm:

new HtmlBundlerPlugin({
  // path to templates
  entry: 'src/views/',

  filename: ({ filename, chunk: { name } }) => {
    // transform 'home/index' filename to output file 'index.html'
    if (name === 'home/index') {
      return 'index.html'; // save as index.html in output directory
    }
    // bypass the original structure
    return '[name].html';
  },
});

Note

In serve/watch mode, you can add/delete/rename a template file in the entry path without restarting Webpack.

↑ back to contents

entryFilter

Filter to process only matching template files. This option works only if the entry option is a path.

Type:

type entryFilter = 
  | RegExp
  | Array<RegExp>
  | { includes?: Array<RegExp>; excludes?: Array<RegExp> }
  | ((file: string) => void | false);

Default value:

{
  includes: [
    /\.(html|eta)$/,
  ], 
  excludes: [] 
}

The default includes property depends on the used preprocessor option. Each preprocessor has its own filter to include from the entry path only relevant template files.

entryFilter as RegExp

The filter works as include only files that match the regular expressions. For example:

new HtmlBundlerPlugin({
  entry: 'src/views/pages/',
  entryFilter: /index\.html$/, // render only `index.html` files in all sub dirs
})

entryFilter as Array<RegExp>

The filter works as include only files that match one of the regular expressions. For example:

new HtmlBundlerPlugin({
  entry: 'src/views/pages/',
  entryFilter: [
    // render only page specifically files, e.g.: `index.html`, `contact.html`, `about.html`
    /index\.html$/,
    /contact\.html$/,
    /about\.html$/,
  ],
})

entryFilter as { includes: Array<RegExp>, excludes: Array<RegExp> }

The filter includes only files that match one of the regular expressions, except excluded files. For example:

new HtmlBundlerPlugin({
  entry: 'src/views/pages/',
  entryFilter: {
    includes: [/\.(html|eta)$/,], // render all `.html` and `.eta` template files
    excludes: [/partial/],  // except partial files
  },
})

entryFilter as callback

In addition to the default includes filter, this filter works as exclude a file if it returns false. If the callback returns true or nothing, then the file will be processed.

For example:

new HtmlBundlerPlugin({
 
3.11.0

10 days ago

3.10.0

15 days ago

3.9.1

23 days ago

3.9.0

26 days ago

3.8.0

28 days ago

3.7.0

1 month ago

3.6.5

2 months ago

3.6.4

2 months ago

3.6.3

2 months ago

3.6.2

2 months ago

3.6.1

2 months ago

3.6.0

2 months ago

3.5.5

2 months ago

3.5.4

2 months ago

3.5.3

2 months ago

3.5.2

2 months ago

3.5.1

2 months ago

3.5.0

3 months ago

3.4.12

3 months ago

3.4.11

3 months ago

3.4.10

4 months ago

3.4.8

4 months ago

3.4.9

4 months ago

3.4.7

4 months ago

3.4.6

4 months ago

3.4.5

4 months ago

3.4.4

4 months ago

3.4.3

5 months ago

3.4.2

5 months ago

3.4.1

5 months ago

3.2.0

5 months ago

2.15.2

6 months ago

2.15.0

7 months ago

2.15.1

6 months ago

3.0.3-beta.0

6 months ago

3.1.3

5 months ago

3.1.2

5 months ago

3.1.1

5 months ago

3.1.0

5 months ago

3.4.0

5 months ago

3.0.3

6 months ago

3.0.2

6 months ago

3.0.1

6 months ago

3.0.0

6 months ago

3.1.0-beta.1

6 months ago

3.1.0-beta.2

6 months ago

3.1.0-beta.3

5 months ago

3.1.0-beta.0

6 months ago

3.3.0

5 months ago

2.16.0-beta.2

6 months ago

2.16.0-beta.1

6 months ago

2.16.0-beta.0

7 months ago

2.11.0

8 months ago

2.13.0

8 months ago

2.12.0

8 months ago

2.14.3

7 months ago

2.14.4

7 months ago

2.14.1

7 months ago

2.14.2

7 months ago

2.14.0

8 months ago

2.8.0

9 months ago

2.10.1

8 months ago

2.10.0

8 months ago

2.9.0

8 months ago

2.2.1

10 months ago

2.2.0

10 months ago

2.2.3

9 months ago

2.4.0

9 months ago

2.2.2

9 months ago

2.6.1

9 months ago

2.6.0

9 months ago

2.0.1

11 months ago

2.0.0

11 months ago

2.3.0

9 months ago

2.1.1

10 months ago

2.5.0

9 months ago

2.3.1

9 months ago

2.7.0

9 months ago

2.5.1

9 months ago

2.1.0

10 months ago

1.18.0

1 year ago

1.16.0

1 year ago

1.17.2

1 year ago

1.17.1

1 year ago

1.17.0

1 year ago

1.17.3

1 year ago

1.15.0

1 year ago

1.2.0

1 year ago

1.1.1

1 year ago

1.14.0

1 year ago

1.1.0

1 year ago

1.13.0

1 year ago

1.0.0

1 year ago

1.12.0

1 year ago

1.6.4

1 year ago

1.9.0

1 year ago

1.6.3

1 year ago

1.8.0

1 year ago

1.6.2

1 year ago

1.7.0

1 year ago

1.6.1

1 year ago

1.5.2

1 year ago

1.6.0

1 year ago

1.5.1

1 year ago

1.5.0

1 year ago

1.4.0

1 year ago

1.3.1

1 year ago

1.3.0

1 year ago

1.2.1

1 year ago

1.1.2

1 year ago

1.6.5

1 year ago

1.11.0

1 year ago

1.10.0

1 year ago

0.10.0

1 year ago

0.1.0

1 year ago

0.3.0

1 year ago

0.2.1

1 year ago

0.2.0

1 year ago

0.10.1

1 year ago

0.9.0

1 year ago

0.8.0

1 year ago

0.9.1

1 year ago

0.5.0

1 year ago

0.4.0

1 year ago

0.7.0

1 year ago

0.6.0

1 year ago

0.5.1

1 year ago

0.0.1-beta.0

1 year ago