/bdsl-webpack-plugin

Browserslist Differential Script Loading webpack plugin.

Primary LanguageJavaScriptMIT LicenseMIT

bdsl-webpack-plugin

NPM version Node version Dependencies status Build status Coverage status Dependabot badge

A webpack plugin that automates generation of the differential script loading with browserslist and browserslist-useragent-regexp.

  1. 🦔 Declare environments in .browserslistrc config like this:
defaults

[modern]
last 2 versions and last 1 year and not safari 12.1

[actual]
last 2 years and not last 2 versions
  1. 📝 Create webpack.config.js for multiple outputs:
function createWebpackConfig(env) {
    return {
        name:    env,
        /* ... */
        module:  {
            rules: [{
                test:    /\.js$/,
                exclude: /node_modules/,
                loader:  'babel-loader',
                options: {
                    cacheDirectory: true,
                    presets:        [
                        ['@babel/preset-env', {
                            modules:     false,
                            useBuiltIns: 'usage',
                            corejs:      3
                        }]
                    ],
                    plugins:        [/* ... */]
                }
            }]
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: 'src/index.html',
                inject:   'head'
            })
        ]
    };
}
  1. 🦄 Add bdsl-webpack-plugin:
const {
    BdslWebpackPlugin,
    getBrowserslistQueries,
    getBrowserslistEnvList
} = require('bdsl-webpack-plugin');

function createWebpackConfig(env) {
    return {
        name:    env,
        /* ... */
        module:  {
            rules: [{
                test:    /\.js$/,
                exclude: /node_modules/,
                loader:  'babel-loader',
                options: {
                    cacheDirectory: true,
                    presets:        [
                        ['@babel/preset-env', {
                            /* ... */
                            targets: getBrowserslistQueries({ env })
                        }]
                    ],
                    plugins:        [/* ... */]
                }
            }]
        },
        plugins: [
            new HtmlWebpackPlugin(/* ... */),
            new BdslWebpackPlugin({ env })
        ]
    };
}

module.exports = [
    ...getBrowserslistEnvList(),
    undefined // to use default .browserslistrc queries
].map(createWebpackConfig);
  1. 🎉 Done! Now index.html will contain differential script loading:
<!DOCTYPE html>
<html>
    <head>
        <title>Example</title>
        <script>function dsl(a,s,c,l,i){c=dsld.createElement('script');c.async=a[0];c.src=s;l=a.length;for(i=1;i<l;i++)c.setAttribute(a[i][0],a[i][1]);dslf.appendChild(c)}var dsld=document,dslf=dsld.createDocumentFragment(),dslu=navigator.userAgent,dsla=[[]];if(/((CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(13[_\.]0|13[_\.]([1-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})[_\.]\d+)(?:[_\.]\d+)?)|(SamsungBrowser\/(9\.2|9\.([3-9]|\d{2,})|([1-9]\d|\d{3,})\.\d+|10\.1|10\.([2-9]|\d{2,})|(1[1-9]|[2-9]\d|\d{3,})\.\d+))|(Edge\/(79|([8-9]\d|\d{3,}))(?:\.\d+)?)|(HeadlessChrome((?:\/79\.\d+\.\d+)?|(?:\/([8-9]\d|\d{3,})\.\d+\.\d+)?))|((Chromium|Chrome)\/(79|([8-9]\d|\d{3,}))\.\d+(?:\.\d+)?)|(Version\/(13|(1[4-9]|[2-9]\d|\d{3,}))\.\d+(?:\.\d+)?.*Safari\/)|(Firefox\/(68|(69|[7-9]\d|\d{3,})|71|(7[2-9]|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Firefox\/(68|(69|[7-9]\d|\d{3,})|71|(7[2-9]|[8-9]\d|\d{3,}))\.\d+(pre|[ab]\d+[a-z]*)?)/.test(dslu))dsl(dsla[0],"/index.modern.js")
else if(/((CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(11[_\.]3|11[_\.]([4-9]|\d{2,})|(1[2-9]|[2-9]\d|\d{3,})[_\.]\d+|12[_\.]0|12[_\.]([1-9]|\d{2,})|12[_\.]4|12[_\.]([5-9]|\d{2,})|(1[3-9]|[2-9]\d|\d{3,})[_\.]\d+)(?:[_\.]\d+)?)|(SamsungBrowser\/(7\.2|7\.([3-9]|\d{2,})|7\.4|7\.([5-9]|\d{2,})|([8-9]|\d{2,})\.\d+|8\.2|8\.([3-9]|\d{2,})|(9|\d{2,})\.\d+))|(Edge\/(17|(1[8-9]|[2-9]\d|\d{3,}))(?:\.\d+)?)|(HeadlessChrome((?:\/65\.\d+\.\d+)?|(?:\/(6[6-9]|[7-9]\d|\d{3,})\.\d+\.\d+)?))|((Chromium|Chrome)\/(65|(6[6-9]|[7-9]\d|\d{3,}))\.\d+(?:\.\d+)?([\d.]+$|.*Safari\/(?![\d.]+ Edge\/[\d.]+$)))|(Version\/(11\.1|11\.([2-9]|\d{2,})|(1[2-9]|[2-9]\d|\d{3,})\.\d+|12\.0|12\.([1-9]|\d{2,})|(1[3-9]|[2-9]\d|\d{3,})\.\d+)(?:\.\d+)?.*Safari\/)|(Firefox\/(59|([6-9]\d|\d{3,}))\.\d+\.\d+)|(Firefox\/(59|([6-9]\d|\d{3,}))\.\d+(pre|[ab]\d+[a-z]*)?)/.test(dslu))dsl(dsla[0],"/index.actual.js")
else dsl(dsla[0],"/index.legacy.js");dsld.all[1].appendChild(dslf)</script>
    </head>
    <body></body>
</html>

Here you can see complete webpack.config.js example.

Install

npm i -D bdsl-webpack-plugin
# or
yarn add -D bdsl-webpack-plugin

⚠️ Before you start ⚠️

  1. bdsl-webpack-plugin captures scripts only from <head> section, so with html-webpack-plugin you should use inject: 'head' option;
  2. By default scripts are loaded asynchronously and executed in "as they defined" order. To execute script in "load-first" order you should add async attribute to <script> tag. For that you can use script-ext-html-webpack-plugin;
  3. defer scripts are not supported, so you can use libraries like when-dom-ready to bootstrap code when DOM ready;
  4. Webpack configs must be in modern to legacy browser order, e.g. ['modern', 'actual', 'legacy'];
  5. bdsl-webpack-plugin also defines process.env.BDSL_ENV variable with bundle's environment.

Why?

There is a differential script loading with module/nomodule trick, for this you can use webpack-module-nomodule-plugin. But browsers that support type=module already have new JS-features with different level of support. For example: optional chaining operator (for comparison browsers with type=module support).

Plugin options

Option Type Default Description
isModule boolean Use type=module support check instead of RegExp. Should be used only on certain build.
browsers string | string[] Manually provide a browserslist query (or an array of queries). It overrides the browserslist configuration specified in your project.
env string When multiple browserslist environments are specified, pick the config belonging to this environment.
ignorePatch boolean true Ignore the difference in patch browser numbers.
ignoreMinor boolean false Ignore the difference in minor browser versions.
allowHigherVersions boolean true For all browsers in the browserslist query, return a match if the useragent version is equal to or higher than the one specified in browserslist.
allowZeroSubverions boolean true Ignore match of patch or patch and minor, if they are 0.
withStylesheets boolean false Enable differential stylesheets loading.
unsafeUseDocumentWrite boolean false Use document.write() to inject <script>. This variant supports defer scripts, but some browsers can restrict document.write() calls.

JS API

Read docs here.

Examples

Metrics

You can get speed metrics from any site using devtool from this repo.

  1. Clone repo:
git clone git@github.com:TrigenSoftware/bdsl-webpack-plugin.git
  1. Install dependencies:
yarn
  1. Now you can run script:
yarn measure \
"https://nodsl.site.com/" \
"https://site.com/"
Output example
https://nodsl.site.com/

Average time: 1s 303ms
Fastest time: 1s 203ms
Slowest time: 2s 432ms
Encoded size: 292 kB
Decoded size: 1.08 MB

https://site.com/

Average time: 1s 274ms
Fastest time: 1s 140ms
Slowest time: 2s 284ms
Encoded size: 218 kB
Decoded size: 806 kB
Parameters

Environment variables:

MEASURE_TIMES - number of site measurements, 10 by default

Options:

--good3g - enable "Good 3G" network preset
--regular4g - enable "Regular 4G" network preset
--cache - enable resource caching