Craft your own d3 build

2022-11-10 09:59:38 +0100 +0100

File this in the maybe-it’s-obvious-to-others-but-not-to-me category: here’s a quick post about how you can craft your own d3.js build with just the d3 packages that you need.

Why

d3’s minified JavaScript file from npmjs is 276kB. We’re using it for a feature on Wikipedia; at that scale, every kilobyte counts for serving users in low bandwidth environments and reducing carbon footprint 🌳.

In our use case, we don’t need all the dependencies. Can we make a smaller build with just the packages we need?

To investigate, my colleague Sergio filed T321215 .

What we did

After more flailing around than I care to admi1t, and some helpful contributions from Eric, this is what I’ve landed on (patch):

Entrypoint to determine which modules from d3 to include

In modules/lib/d3/index.js we have:

export * from "d3-array";
export * from "d3-interpolate"; export * from "d3-path";
export * from "d3-scale";
export * from "d3-selection";
export * from "d3-shape";
export * from "d3-time";
export * from "d3-time-format";

package.json

I added the relevant d3`` subpackages to devDependenciesinpackage.json`:

"devDependencies": {
	"d3-array": "3.2.0",
	"d3-interpolate": "3.0.1",
	"d3-path": "3.0.1",
	"d3-scale": "4.0.2",
	"d3-selection": "3.0.0",
	"d3-shape": "3.1.0",
	"d3-time": "3.0.0",
	"d3-time-format": "4.1.0",
}

For convenience, I also added a scripts entry for rollup aliased to rollup -c.

rollup.config.js

The complicated part was figuring out the correct rollup config. After quite a bit of experimentation, we ended up with:

'use strict';
const resolve = require( '@rollup/plugin-node-resolve' );
const terser = require( '@rollup/plugin-terser' );
/**
 * Config file for generating a custom build of d3, containing only the packages
 * we need for the Impact module.
 */
module.exports = {
	input: 'modules/lib/d3/index.js',
	output: {
		file: 'modules/lib/d3/d3.min.js',
		format: 'umd',
		compact: false,
		name: 'd3'
	},
	plugins: [
		resolve(),
		terser()
	],
	// upstream d3 rollup.config.js has this as well.
	onwarn( message, warn ) {
		if ( message.code === 'CIRCULAR_DEPENDENCY' ) {
			return;
		}
		warn( message );
	}
};

Mysteriously, not including the terser plugin results in a broken build. (We don’t need the code minified here since MediaWiki’s ResourceLoader would do that for us.)

⚡️ Build!

Now npm run rollup generates a d3.min.js file in modules/lib/d3/d3.min.js. The end result is a reduction from 278kB to 115kB.


  1. You’re welcome to visit the history of the patch and its child patches 😅 ↩︎