Generate a Critical JavaScript Bundle Using Rollup
Tags: webdev, webperf, javascript, rollup
Published: 2020-02-21 16:07:33

For performance and user experience reasons there are certain tasks that we want to complete before DOM content, stylesheets, or scripts have loaded. Critical JavaScript, inlined into `<script>` blocks in the `<head>` of an HTML document, is a pattern for achieving this:-

```html
<!DOCTYPE html>
<html>
  <head>
    <script>
      console.log("I am critical!");
    </script>
  </head>
</html>
```

Examples of such tasks include running CSS and JavaScript feature tests, loading polyfills, and performing conditional logic.

## Keep Things Simple

Given that Critical JavaScript is inlined directly into the `<head>` of _every_ HTML response, it's essential that we keep the bundle size small.

A step towards this is keeping the code simple with respect to the JavaScript and DOM APIs used and in-turn disabling transpilation tools from doing anything other than syntax transformation (see below for instructions on how to configure [Babel](https://babeljs.io/)).

Configuring [Browserslist](https://github.com/browserslist/browserslist) to reflect your support matrix will allow for the use of complimentary tools such as [`eslint-plugin-compat`](https://github.com/amilajack/eslint-plugin-compat) to ensure that you're not using unsupported features in production.

All of this means you can leverage modern ES6 syntax such as [Arrow Functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), but omit JavaScript APIs such as [`Promise`s](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and DOM APIs like [`Element.classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList). However, the syntax and features available to you are entirely dependent on your own browser support matrix.

If polyfilling a feature is unavoidable then I would recommend doing so by directly `import`ing specific modules from [`core-js`](https://github.com/zloirock/core-js/tree/master/packages/core-js/modules).

## Why Rollup

There’s no 2-ways about it: [Rollup](https://rollupjs.com) creates bundles that are simpler and smaller than those created by alternatives such as [Webpack](https://webpack.js.org).

The following is a starting point for how to best configure Rollup to output `critical.js` bundles with the smallest footprint:-

```js
// rollup.config.js
import path from "path";

import resolve from "@rollup/plugin-node-resolve";
import commonJs from "@rollup/plugin-commonjs";
import replace from "@rollup/plugin-replace";
import babel from "rollup-plugin-babel";
import { terser } from "rollup-plugin-terser";
import filesize from "rollup-plugin-filesize";

const { NODE_ENV = "development" } = process.env;

const isProduction = NODE_ENV === "production";
const ANALYZE = process.env.ANALYZE ? process.env.ANALYZE === "true" : false;

export default {
  input: path.join(process.cwd(), "critical.js"),

  output: {
    dir: path.join(process.cwd(), "critical.min.js"),
    format: "iife",
    sourcemap: isProduction ? false : "inline"
  },

  plugins: [
    replace({
      "process.env.NODE_ENV": JSON.stringify(NODE_ENV)
    }),

    resolve({
      browser: true
    }),

    commonJs({
      include: "node_modules/**"
    }),

    babel({
      exclude: "node_modules/**",
      babelrc: false,
      presets: [
        [
          "@babel/preset-env",
          {
            debug: ANALYZE,
            modules: false,
            useBuiltIns: false
          }
        ]
      ]
    }),

    isProduction && terser(),

    isProduction && filesize()
  ].filter(Boolean)
};
```

Below is just an example of some of the tasks that you might perform via `critical.js`. It's by no means comprehensive, but it's a good example of the purpose of this category of JavaScript bundle.

```js
// critical.js
// polyfill asynchronously loading CSS from `<link rel="preload" />` stylesheets
import "fg-loadcss/src/cssrelpreload";

// find out more about `font-display` here:
// https://css-tricks.com/font-display-masses/#article-header-id-3
import supportsFontDisplay from "~/utilities/supports/font-display";

const htmlClassNames = ["js"];

if (supportsFontDisplay) {
  htmlClassNames.push("supports-font-display");
}

// overwrites any existing classes on the `<html>` element (e.g. `"no-js"`)
document.documentElement.className = htmlClassNames.join(" ");
```

This pattern is especially useful when used in conjunction with custom web fonts in order to control [FOUT, FOIT, and FOFT](https://css-tricks.com/fout-foit-foft/).

## Inlining the Script

The final step, and the one that I can write the least about, is inlining the compiled `critical.min.js` into your HTML. The mechanism for this will vary drastically depending on your technology stack and deployment processes.

## Field Notes

Are you currently using this approach in your sites and applications? If so, how has your experience been so far? Do you plan to implement Critical JavaScript in the future? What kinds of tasks do you think this is suited to?