tooling.report

feature

JS entry-point hashing cascade

Do dependency changes update entry bundle hashes?

Person in shorts with blue hair walking left

Introduction

When bundling a multi-page application, pages often have different "entry" modules - / loads an index.js module, /profile loads a profile.js module, etc. Dependency modules that are shared by these entries can be extracted into shared bundles via Code Splitting. When adopting hashed URLs for effective caching, it's important that any changes to shared bundles update the entry bundles to reference the newly hashed URLs.

The Test

This test simulates a two-page application, with index.js and profile.js entry modules for each page. A shared utils.js module is depended on by both entry modules, and Code Splitting is enabled such that it will be extracted into its own bundle.

index.js

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

profile.js

import { logCaps } from './utils.js';
logCaps('This is profile');

utils.js

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

A build should produce three files: the index.<hash>.js and profile.<hash>.js entry bundles, and a utils.<hash>.js bundle depended on by both. Changing the contents of utils.js and re-building results in its hashed URL being updated, which should in turn change the hashed URLs of the two entry bundles.

Note: Some tools place hashed URLs in a centralized mapping rather than in bundles. For these, hash changes only need to be propagated to that registry.

Conclusion

browserify

Browserify supports extracting a single bundle for shared dependencies of entry modules using factor-bundle. When bundle URLs are hashed using gulp-hash, changes to shared dependencies also change the hash in the "common" bundle URL. Since factor-bundle does not provide a mechanism for loading bundle dependencies in the browser, injecting the required bundles into a page must be done manually. This means there are no references to the hashed URL of the common bundle in any other bundles, so their hashes do not need to be updated when the hash of the common bundle is updated

The result is that Browserify neither passes nor fails this test, since it does not support dependency loading.

parcel

Parcel inlines <script src> for static dependencies, which avoids the hash cascade.

With <script type="module" src>, the cascade can't be avoided, but it's correctly handled.

Issues

rollup

Rollup extracts shared dependencies of entry modules into common bundles. Changing a shared dependency updates its bundle's hashed URL, which means any entry bundles that reference that URL are also updated with newly hashed URLs.

webpack

In Webpack, a changed hash for the bundle shared by multiple entries doesn't in turn change those entries' bundle hashes. While this may seem like a bug at first, the entry bundles don't actually contain any reference to the shared dependency bundle, and thus their contents do not change when a shared dependency is updated. This is because Webpack does not include entry dependency bundle references in entry bundles or its runtime bundle loader, but rather assumes the requisite script loading will be handled by a tool like html-webpack-plugin.

Webpack 5 will introduce entry.dependsOn to specify shared dependency bundles for each entry, which may change whether it passes or bypasses this particular test.