Home

Mike Bostock edited this page Sep 1, 2016 · 18 revisions
Clone this wiki locally

SMASH DEPRECATED. DO ROLLUP INSTEAD.

SMASH is a temporary solution to JavaScript’s lack of native modules. There are lots of alternative module implementations as we await the standardization and adoption of ES6 modules; depending on your needs, those other solutions might be better-suited for you. I wrote SMASH because I was already concatenating files for D3, and with a bit of metadata in the way of import statements, it was a small step to achieve the file-savings of minimal custom bundles. In the example described below, the minified library was reduced from 176K to 72K, down 60%! In the long term, I think Browserify and Node.js-style require is probably the right solution, though refactoring code that uses "package-private" style sharing of internal variables to strict modules is tedious and adds a bit of overhead. The ES6 Module Transpiler is also appealing, though unlike Browserify, ES6 would require an additional shim to use in Node.js; as far as I can tell, node --harmony_modules doesn’t support the current ES6 modules proposal.

How-To

Say you’re making something with D3, TopoJSON and Queue. Here’s how to smash together a minimal JavaScript library bundle with only the parts you need.

First, use NPM to install the various modules you need. (If you don’t have npm installed already, you can use Homebrew or the official installer.) In addition to the modules mentioned above, you'll need the SMASH and UglifyJS modules:

npm install d3 topojson queue-async smash uglify-js

Or you can put this in a package.json and npm install:

{
  "name": "anonymous",
  "version": "0.0.1",
  "dependencies": {
    "d3": "3",
    "queue-async": "1",
    "smash": "0",
    "topojson": "1",
    "uglify-js": "2"
  }
}

Next, create a Makefile. Inside, define a variable called LIBRARY_FILES that lists all of the files that you need. We’re including all of TopoJSON and Queue (because they’re small enough to be single modules), but only the specific parts of D3 that we need; i.e., the parts of D3 that we refer to directly from our loading HTML file. For example, if we use d3.json, then we’ll need to include D3’s xhr/json.js as a dependency.

LIBRARY_FILES = \
	node_modules/d3/src/start.js \
	node_modules/d3/src/compat/index.js \
	node_modules/d3/src/arrays/map.js \
	node_modules/d3/src/arrays/nest.js \
	node_modules/d3/src/format/format.js \
	node_modules/d3/src/geo/conic-conformal.js \
	node_modules/d3/src/geo/path.js \
	node_modules/d3/src/geom/quadtree.js \
	node_modules/d3/src/layout/force.js \
	node_modules/d3/src/scale/sqrt.js \
	node_modules/d3/src/selection/selection.js \
	node_modules/d3/src/transition/transition.js \
	node_modules/d3/src/xhr/json.js \
	node_modules/d3/src/end.js \
	node_modules/topojson/topojson.js \
	node_modules/queue-async/queue.js

D3 needs to be wrapped in its start.js and end.js files to compile correctly. (Perhaps smash will use an exports variable in the future.)

Optionally include compat/index.js just after start.js to include all browser compatibility modules.

Then, add a rule for building lib.js by smashing together these files and then minifying them:

lib.js: $(LIBRARY_FILES)
	node_modules/.bin/smash $(LIBRARY_FILES) | node_modules/.bin/uglifyjs - -c -m -o $@

To run the rule, type this into the terminal:

make lib.js

That’s it! Now you can <script src="lib.js"></script> and have access to all your modules! Since the modules won’t change unless you update them or add new dependencies, you don’t need to rebuild the library bundle during normal development. (You could combine your own code into this bundle, but this may not be worth the effort if your code size is small compared to the libraries on which you depend.)