Alexander Gromnitsky's Blog

Commonjs + node polyfills in rollup & esbuild

Latest update:

This is what I need from a bundler:

  1. Import ES6 modules.

  2. Wrap node built-in modules (that don't do IO) to work in a browser, like browserify did (still does).

  3. Import commonjs modules.

  4. Don't require hours of reading its manual & scavenging github for 'plugins'.

Why are there no bundlers that do that w/o any config? Why can't I write

$ some-js-bundler < main.js > bundle.js

Example

$ cat foo.js
import path from 'path'
import md5 from 'blueimp-md5' // nodejs implementation is too big

export function foo() {
    return md5(path.dirname("foo/bar/baz.txt"))
}
$ cat main.js
import * as lib from './foo.js'

console.log(lib.foo())
$ echo '{"type":"module","dependencies":{"blueimp-md5":"*"}}' > package.json
$ npm i -s
$ node main.js
82d0f0fa8551de8b7eb5ecb65eae0261

Now we bring this main.js to the browser.

rollup

The good thing about it that you can achieve that w/o a config. The bad thing: I believe node polyfills are outside of a supported plugins realm.

3 plugins are required:

$ npm i -s @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-polyfill-node

Then

$ rollup main.js --format iife -p rollup-plugin-polyfill-node -p @rollup/plugin-node-resolve -p @rollup/plugin-commonjs > rollup.1.js

An unminified bundle:

$ wc -c < rollup.1.js
19678

The same with a config?

$ cat rollup.config.js
import nodePolyfills from 'rollup-plugin-polyfill-node'
import {nodeResolve} from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

export default {
    output: { format: 'iife' },
    plugins: [nodePolyfills(), nodeResolve(), commonjs()]
}
$ rollup main.js -c > rollup.2.js
$ cmp rollup.1.js rollup.2.js
$ exit $?
0

The fact that you need explicitly tell rollup to use a config, even if your named it in a 'recommended' way, annoys me immensely.

esbuild

Good news: no need for a node or commonjs plugin! Bad news: esbuild mainterners don't particularly care about node polyfills, and the most maintainable plugin actually reuses the rollup package.1

Even worse news: as long as you use an external plugin, say goodbye to the standard esbuild CLI--writing an esbuild 'wrapper' is required.

$ npm i -s esbuild @esbuild-plugins/node-modules-polyfill
$ cat esbuild.js
#!/usr/bin/env node

import { build } from 'esbuild'
import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill'

build({
    plugins: [NodeModulesPolyfillPlugin()],
    entryPoints: [process.argv[2] || usage()],
    bundle: true,
})

function usage() { console.warn('Usage: esbuild.js file.js'), process.exit(1) }
$ chmod +x esbuild.js

I understand philosophical reasons for writing your own tooling every time, but this is too common a case.

$ ./esbuild.js main.js > esbuild-bundle.js
$ wc -c < esbuild-bundle.js
13998

Anyway, with no additional tweaking, rollup seems to make slightly smaller bundles than esbuild:

$ for i in rollup.1.js esbuild-bundle.js ; do
> terser $i --module -mc | gzip -c | wc -c
> done
2454
2716

  1. Maybe it's a good thing, for another bespoke attempt of copypasting node's stdlib makes little sense.

Tags: ойті
Authors: ag