numical / script-ext-html-webpack-plugin

Enhances html-webpack-plugin functionality with different deployment options for your scripts including 'async', 'preload', 'prefetch', 'defer', 'module', custom attributes, and inlining.
MIT License
588 stars 105 forks source link

Script Extension for HTML Webpack Plugin

npm version Dependency Status Build status js-semistandard-style

NPM

Deprecation Warning

tl;dr
This project is no longer maintained. It does not support Webpack 5.

A bit more detail
Any look at the project activity will show that I have not been able to maintain this project adequately.
The advent of version 5 of Webpack requires another bout of refactoring that I simply have no time for.
Consequently v2.15.0 will be the last version of this plugin. My thanks to all users, and especially to all contributors, of this plugin over the years.
My apologies to all those whose webpack 5 migration has been made more complicated by this decision.

But I still want to use the plugin...
Feel free!
My last update works with versions of v4.44.2 of webpack and v4.5.0 of html-webpack-plugin.
Forkers feel free! That's what the licence is for.
In fact, if you fork with an intention to support on-going development, let me know! I'll happily link to your repository here and offer some tips (main one: ditch backward compatibility - it's a pain).
I will formally archive this repository at the end of the 2020.

Summary

Enhances html-webpack-plugin functionality with different deployment options for your scripts including:

This is an extension plugin for the webpack plugin html-webpack-plugin - a plugin that simplifies the creation of HTML files to serve your webpack bundles.

The raw html-webpack-plugin incorporates all webpack-generated javascipt as synchronous<script> elements in the generated html. This plugin allows you to:

Installation

You must be running webpack (1.x, 2.x, 3.x, 4.x) on node 6+. Install the plugin with npm:

$ npm install --save-dev script-ext-html-webpack-plugin

Not that you will need v3.0.6+ or v4.x of html-webpack-plugin

For those requiring earlier versions of node, please use the last 1.x version of this plugin. However please note this does not have webpack 4.x support:

$ npm install --save-dev script-ext-html-webpack-plugin@1.8.8

You may see an UNMET PEER DEPENDENCY warnings for webpack and various plugins.

This is fine; in testing, we dynamically download multiple versions of webpack (via the dynavers module).

Basic Usage

Add the plugin to your webpack config as follows:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin()
]  

The order is important - the plugin must come after HtmlWebpackPlugin.

The above configuration will actually do nothing due to the configuration defaults.

Some more useful scenarios:

All scripts set to async:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    defaultAttribute: 'async'
  })
]  

All scripts set to async except 'first.js' which is sync:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    sync: 'first.js',
    defaultAttribute: 'async'
  })
]  

Configuration offers much more complex options:

Configuration

You must pass a hash of configuration options to the plugin to cause the addition of attributes:

A script matching pattern matches against a script's name. It can be one of:

In more complicated use cases it may prove difficult to ensure that the pattern matching for different attributes are mutually exclusive. To prevent confusion, the plugin operates a simple precedence model:

  1. if a script name matches theinline script matching pattern, it will be inlined;

  2. if a script name matches the sync script matching pattern, it will have no attribute, unless it matched condition 1;

  3. if a script name the async script matching pattern, it will have the async attribute, unless it matched conditions 1 or 2;

  4. if a script name matches the defer script matching pattern, it will have the defer attribute, unless it matched conditions 1, 2 or 3;

  5. if a script name does not match any of the previous conditions, it will have the `defaultAttribute' attribute.

The module attribute is independent of conditions 2-5, but will be ignored if the script isinlined.

Dynamically Loaded Scripts

The preload and prefetch configuration also have allow an additional property in the hash form that can be passed to include dynamically loaded (asynchronous) scripts. This property is chunks and can have one of the following String values:

Configuration Examples

All scripts with 'important' in their name are sync and all others set to defer:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    sync: 'important',
    defaultAttribute: 'defer'
  })
]  

Alternatively, using a regular expression:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    sync: /important/,
    defaultAttribute: 'defer'
  })
]  

All scripts with 'mod' in their name are async and type 'module', all others are sync (no explicit setting for this as it is the default):

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    async: 'mod',
    module: 'mod'
  })
]  

Script 'startup.js' is inlined whilst all other scripts are async and preloaded:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    inline: 'startup',
    preload: /\.js$/,
    defaultAttribute: 'async'
  })
]  

All scripts are preloaded with a crossorigin attribute set to enable CDN's:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    custom: {
      test: /\.js$/,
      attribute: 'crossorigin',
      value: 'anonymous'
    },
    preload: {
      test: /\.js$/
    }
  })
]  

All asynchronous scripts are added as preload resource hints. All other scripts are async:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    async: /\.js$/,
    preload: {
      test: /\.js$/,
      chunks: 'async'
    }
  })
]  

All scripts have custom attribute type='text/paperscript' and ui.js also has a custom attribute of id='1235':

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    custom: [
      {
        test: /\.js$/,
        attribute: 'type',
        value: 'text/paperscript'
      },
      {
        test: 'ui.js',
        attribute: 'id',
        value: '12345'
      }
    ]
  })
]  

And so on, to craziness:

plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    inline: 'startup',  
    sync: [/imp(1|2){1,3}}/, 'initial'],
    defer: ['slow', /big.*andslow/],
    module: [/^((?!sync).)*/, 'mod'],
    prefetch: 'indirectly-referenced.js',
    defaultAttribute: 'async'
  })
]  

Any problems with real-world examples, just raise an issue.

A Note on Script Names

In the above examples the actual script names are used to select the deployment option. You may not wish to couple asset names to your deployment like this. Instead you can use Webpack's entry configuration to create aliases that the plugin will then use for its pattern matching. Your webpack.config.js will look something like this:

entry: {
  a: path.join(__dirname, 'lib/myFunctions.js'),
  b: path.join(__dirname, 'lib/otherFunctions.js'),
  c: path.join(__dirname, 'lib/criticalFuntions.js')
},
output: {
  ...
  filename: '[name].js'
}
plugins: [
  new HtmlWebpackPlugin(),
  new ScriptExtHtmlWebpackPlugin({
    inline: ['c'],  
    defer: ['a', 'b']
  })
]  

Inlining

Several notes and caveats apply:

Resource Hints

In most cases, modern browsers will intelligently preload referenced script assets. However if you wish, this plugin can add resource hint elements to the <head> element of the form:

<link rel="[preload|prefetch]" href="https://github.com/numical/script-ext-html-webpack-plugin/blob/master/[scriptname]" as="script">

Use the preload and prefetch configuration options. Where preload and prefetch patterns overlap, preload takes precedence.

Possibly a more compelling use case is to preload/prefetch dynamically loaded scripts generated by Webpack's code splitting. Since v1.7.0, this plugin can do this - see 'Dynamically Loaded Scripts' above.

Notes:

Change History

v2.1.5

v2.1.x

v2.0.x

v1.8.x

v1.7.x

v1.6.x

v1.5.x

v1.4.x

v1.3.x

v1.2.x

v1.1.x

v1.0.x