paint-brush
The Right Way to Utilize Webpack for Bundling a HTML Page With CSS and JSby@biodiscus
4,590 reads
4,590 reads

The Right Way to Utilize Webpack for Bundling a HTML Page With CSS and JS

by biodiscusFebruary 13th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The new HTML Bundler plugin makes Webpack setup incredibly simple. All the config happens in one place. All source styles and scripts specified in HTML are processed, and the extracted JS and CSS are saved to the output directory. The plugin automatically substitutes output filenames into the generated HTML file.
featured image - The Right Way to Utilize Webpack for Bundling a HTML Page With CSS and JS
biodiscus HackerNoon profile picture


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, EtaHandlebarsNunjucksout 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.