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.

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

When targeting the web, hash changes for a bundle shared by multiple entries do not 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. This behavior also works when using entry.dependsOn to specify shared dependency bundles for each entry.

When targeting Node.js, entry bundles reference and load shared bundles directly, since parallel loading via <script> is not possible. This means shared bundle changes affect entry bundle hashes, however filename hashing has limited applicability in this context.