tooling.report

feature

Preloading script dependencies

Can preloads be generated for scripts?

Person in shorts with blue hair walking left

Introduction

Creating split points using dynamic imports makes it possible to progressively load parts of the code for an application as-needed. The ability to preload code-splitted bundles can improve performance by increasing the likelihood that bundles are available by the time they are needed. However, this is not always optimal: preloading scripts that are loaded conditionally or that may never be needed incurs an unnecessary cost.

The Test

This test simulates a three-page website with index profile and help pages, each having their own corresponding script. The page scripts depend on two common utility modules, and each build tool is configured to preserve these modules in their own bundles as runtime dependencies. Finally, the script for the index page lazy-loads a module using dynamic import after a 10 second delay, and the resulting code-splitted bundle should not be preloaded.

index.js

import { logCaps, strongEm } from './utils.js';
logCaps(strongEm('This is index'));

setTimeout(async () => {
  const { exclaim } = import('./index-exclaim.js');
  logCaps(exclaim('This is still index'));
}, 10000);

index-exclaim.js

export function exclaim(msg) {
  return msg + '!';
}

profile.js

import { em } from './more-utils.js';
console.log(em('This is profile'));

help.js

import { logCaps, strongEm } from './utils.js';
logCaps(strongEm('This is help'));

utils.js

import { em } from './more-utils.js';

export function logCaps(msg) {
  console.log(msg.toUpperCase());
}

export function strongEm(msg) {
  return '===' + em(msg) + '===';
}

more-utils.js

export function em(msg) {
  return '***' + msg + '***';
}

The result of this test should be 3 HTML pages: index.html, profile.html and help.html. Each page should load its respective script: index.js, profile.js, and help.js.

In order to pass the test, each page should contain <link rel="preload"> tags for the scripts it immediately needs, but not for its lazy-loaded dependencies. For example, index.html should preload the index.<hash>.js and utils.<hash>.js bundles, but not the index-exclaim.<hash>.js bundle.

Conclusion

browserify

Although Browserify have factor-bundle plug in it is not build for complex deps splitting like this usecase

Issues

  • N/A
parcel

Parcel inserts <script> elements for the shared dependencies into each HTML page so that they are loaded in parallel with the entry scripts. There is no need for <link rel="preload"> elements, because the scripts are loaded immediately.

rollup

Rollup's generateBundle hook gives you full access to build details before files are written. For script chunks, this includes a list of files imported by that script, which you can use to create preloads.

webpack

html-webpack-plugin exposes Webpack's generated assets to a template and set of plugins, which includes information about required bundles for a given entry module. This can be used to inject the necessary preloads or script tags for all dependencies of a page.

While the available chunk metadata passed through templates and plugins does include information on code-splitted bundles (async chunks) required by each entry bundle, no plugins are currently available for injecting only the necessary preloads or script tags to based on this information. At present, it is possible to preload all code-splitted bundles or a subset based on filename matching, not based on actual usage.