html-webpack-plugin 4 has been released!

jantimon

Jan Nicklas

Posted on March 23, 2020

html-webpack-plugin 4 has been released!

It took way too long but finally the new major version of the html-webpack-plugin is making it's leap from beta to a stable release!

Performance!

FlameGraph

One big goal for this release was to improve the performance during development and production builds.

The performance boost was gained mainly by dropping the usage of compilation.getStats().toJson() thanks to a new API provided by the webpack core team around @sokra. This new API provides all information necessary to inject the scripts and styles into the html code.

Unfortunately relying on that API means that webpack 1-3 can not be supported anymore.

To further increase the performance the entire caching approach was rebuilt from scratch to decrease the compilation efforts.

The alpha tester feedback was great!

#953 "The best of 5 total build time goes down from 10.41s (with 4.0.0-alpha) to 10.29s - which is now only 130ms slower than when not using the plugin at all :-)"

#962: "For reference, my project (which has grown substantially since #962) builds in ~8000ms without and ~1000ms with these changes."

CompileTime

While working on those performance improvements I wrote a cpuprofile-webpack-plugin - a small util to analyse your webpack build performance for production build but even more importantly for recompilations during development.

Feel free to give it a try and let me know what you think :)

Template language support

Since html-webpack-plugin 2.x has been able to use the loaders specified inside the webpack config file. Therefore it is not only capable of compiling .ejs templates but any code which can be transpiled with a webpack loader (hbs, ejs, twig, dust, pug, htl, js, ts, jsx, tsx ...).

A javascript or jsx template allows even to generate a static server side rendered version of your application.

html-webpack-plugin template:

import ReactDOMServer from 'react-dom/server';
import React from 'react';
import { App } from './App';

export default () => `
  <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>Server Side Rendering Demo</title>
    </head>
    <body>
      <div id="root">${ReactDOMServer.renderToString(<App />)}</div>
    </body>
    </html>
`

For further details on how to connect loaders to the html-webpack-plugin checkout the template option docs or take a look at the html-webpack-plugin jsx codesandbox.

Another way to use the html-webpack-plugin to prerender a static page out of your app is the prerender-loader from @developit.

Meta Tags

The html-webpack-plugin is now able to inject meta-tags without writing custom templates:

new HtmlWebpackPlugin({
  meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'},
})

Base Tags

Similar to the meta tags it is now also possible to add a base tag:

new HtmlWebpackPlugin({
  base: '/',
})

Custom Template with Zero Config

Customizing the template is now possible without configuration.

From version 4 the html-webpack-plugin will look for a local src/index.ejs file. If such a file can be found it will be used as a template:

Zero Config Template

Minification by default

Thanks to the work by @edmorley we were able to enable html minification by default if webpack is running in production mode. This behaviour can be disabled by adding minification: false to the html-webpack-plugin configuration.

Enable minification by default when 'mode' is production #1048

Previously minification was disabled by default. Now, if minify is undefined and mode is 'production', then it is enabled using the following options:

{
  collapseWhitespace: true,
  removeComments: true,
  removeRedundantAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
  useShortDoctype: true
}

These options were based on the settings used by create-react-app, Neutrino and vue-cli, and are hopefully fairly conservative. See: https://github.com/jantimon/html-webpack-plugin/issues/1036#issuecomment-421408841 https://github.com/kangax/html-minifier#options-quick-reference

These same defaults can enabled regardless of mode, by setting minify to true (which previously passed an empty object to html-minifier, meaning most minification features were disabled). Similarly, minification can be disabled even in production, by setting minify to false.

This change has no effect on users who pass an object to minify.

Fixes #1036.

minification demo

Allow template variable modification

Evan You asked for a better way to modify which values are sent down to the template:

feat(template): support custom template params #830

This allows the user to inject custom variables to be used in the template interpolation, e.g. simplifying webpackConfig.output.publicPath to something shorter.

Docs/tests are not included, but if this sounds like a good idea I can add those upon request.

The outcome was a new templateVariables option which allows to add additional data e.g. process.env to the values which are sent down to the template:

TemplateVariables

Nonblocking script loading

Until now all script tags were added at the end of the body tag.

However now that all modern browsers allow to load javascript in parallel without pausing the html parsing the scriptLoading option can speed up your users page loads.

defer

Usage:

new HtmlWebpackPlugin({
  scriptLoading: 'defer'
})

New Hooks

The webpack core team asked to upgrade to the new hook system to further increase the webpack build speed.

These hooks allow plugin developers to change the default behaviour of the html-webpack-plugin. The following chart shows the flow and the hooks (beforeAssetTagGeneration, alterAssetTags, alterAssetTagGroups, afterTemplateExecution, beforeEmit, afterEmit):

Hook flow

Here is an example for a plugin which manipulates the generated html file in the beforeEmit hook:

const HtmlWebpackPlugin = require('html-webpack-plugin');

class MyPlugin {
  apply (compiler) {
    compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
      // Static Plugin interface |compilation |HOOK NAME | register listener 
      HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
        'MyPlugin', // <-- Set a meaningful name here for stacktraces
        (data, cb) => {
          // Manipulate the content
          data.html += 'The Magic Footer'
          // Tell webpack to move on
          cb(null, data)
        }
      )
    })
  }
}

module.exports = MyPlugin

For more information please take a look at the readme events section

Contributors

People around the world are approaching me to ask for help, suggest and work on new features, fix a typo or even fix entire problems. Thank you so much and please keep it up! :)

If you have any feedback for this release create an issue or contact me on twitter @jantimon (direct messages are open).

Sponsors

Big thanks to all the sponsors who supported the development over the last years.

Especially TipeIO and Principal Financial Services, Inc

Sponsors

Full changelog

The changelog with all changes can be found directly on github

What is coming up next?

The next goal is to be fully compatible with the Webpack 5. Especially with the new Webpack 5 FileSystemInfo API to solve

.

Another goal would be to further improve browser load times. For browsers with support for preloading @sokra proposed an even faster approach than scriptLoading: 'defer'.

💖 💪 🙅 🚩
jantimon
Jan Nicklas

Posted on March 23, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related