Custom browser-compatible format

Can bundles use a custom module loader?

Person in shorts with blue hair walking left


Some older browsers don’t support ECMAScript modules or dynamic import, which are fundamental to code splitting and lazy loading. Despite the decline in usage of these browsers, some developers still have to support them. While it is possible to polyfill ECMAScript modules in these browsers to a large extent, doing so can incur performance overhead. In cases where performance and compatibility are paramount, a custom custom format like AMD or proprietary loader like Webpack’s runtime can be used.

The Test

This test bundles three JavaScript modules: an entry module with a dependency, and a dynamically imported module that will be code-splitted. Each build tool is configured to use either its built-in custom module format or the most popular custom module format for that tool.


import { logCaps } from './utils.js';

async function main() {
  const { exclaim } = await import('./exclaim.js');
  logCaps(exclaim('This is index'));


export function logCaps(msg) {


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

The result should be two bundles: an entry bundle containing the code from index.js and utils.js, and another containing exclaim.js. The second bundle should be dynamically loaded by the custom module format's runtime implementation when main() is called.



Browserify's default behavior is to take CommonJS modules and bundle them to run in a browser. However, all require() are inlined and there doesn’t seem to be de-facto standard on how to do dynamic require().


This is the default behavior for Parcel, where it compiles to a CommonJS-like format with a custom loader.


Rollup supports RequireJS and SystemJS out of the box.


Webpack's default output format is a custom browser-based runtime. This enables it to perform dynamic module loading in all browsers, and is the means by which Webpack implements its cross-bundle dependency sharing.