Bundling With Rollup—The Basics

If you’re writing a module in Node, chances are you’re using CommonJS syntax that looks like this:

// sayhi.js
var d3 = require('d3');
function sayHi() { 
d3.select('body').html('hey you guuuuys!');
}
module.exports = sayHi;

To run this code in the browser, you need browserify or webpack to create a bundle that includes your code and external libraries in a file that browsers understand.

// index.js
var sayHi = require('./sayhi');
sayHi();
// Terminal
$ browserify index.js > bundle.js
// index.html
<script src="bundle.js"></script>

In the process you can also use transforms to convert ES6 syntax to ES5, and a handful of other things. I’m simplifying of course, but this pretty much captures the status quo.

Write code ↦ create a bundle ↦ run code in the browser.

But CommonJS syntax has its limitations, and in any case ES6 introduces a new module syntax that allows for some great flexibility and better optimization. Unfortunately, neither Node nor browsers currently support this syntax so we need a tool to convert it either to require() for Node, or a browser bundle.

Side note: extolling the virtues of ES6 modules is beyond the scope of this post. Going forward I’m going to assume you’re interested in making the switch and focus on how to do it.

Rollup

Rollup is similar to Browserify in that it creates a bundle that includes your code, external libraries (like D3), and optionally performs some inline transforms. The main difference is that it expects files to be written in the ES6 module format.

// input
import * as d3 from 'd3';
// output -- commonJS
var d3 = require('d3');
// output -- IIFE (browser-friendly function)
(function (d3) { 'use strict'; }(d3));

As with most tools, there are several ways to get from here to there. Here are some of the basic approaches.

A First Pass

Let’s walk through an example of how you would create a package that uses D3 and transform it into a browser-friendly bundle.

$ npm install -g rollup

Let’s create a file, say index.js, and enter our code in ES6 module syntax.

import * as d3 from 'd3';
export default function sayHi() { 
d3.select('body').html('Hey you guuuys!');
}

We’re ready to create a bundle. To make it browser-executable, we’ll specify the IIFE output format (Instantly Invoked Function Expression).

$ rollup -f iife --name HiSayer -o bundle.js -- index.js
Treating 'd3' as external dependency
No name was provided for external module 'd3' in options.globals – guessing 'd3'

If you look in bundle.js now, you’ll see the converted code.

The warning messages are telling us that Rollup encountered a call to import an external module called “d3” that it couldn’t locate. Since we didn’t declare explicitly that we want it left out of the bundle, it assumed that it’s going to exist in the global scope and went on with packaging the rest of our code. Incidentally, the reason “d3” wasn’t found is because Rollup does not follow Node’s path resolution algorithm by default. For that we’ll need to use plugins (more on that below).

If we intend to exclude D3 from our bundle and instead provide it via the global namespace, it’s better to make this explicit.

$ rollup --globals d3:d3 <...other options...>

We’ll then need to make sure D3 is available to our code.

<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="bundle.js"></script>

If we do want to bundle D3 along with our code, say to reduce HTTP requests in production, we need to extend Rollup’s capabilities so it can find Node modules the way Node does.

Bundling Third-Party Libraries

Rollup’s functionality can be extended using plugins. However, we can’t do that via the command line interface. Instead we create a config file and export a configuration object, specifying the same options as before but adding calls to the plugins.

First, let’s install a plug-in that allows Rollup to find third-party libraries in the node_modules directory.

$ npm install --save-dev rollup-plugin-node-resolve

Then create rollup.config.js, importing and invoking the plugin before bundling takes place.

// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
export default {
input: 'index.js',
output: {
file: 'bundle.js',
format: 'iife',
name: 'HiSayer'
},
plugins: [
resolve({ jsnext: true })
]
};

To use this file we simply run Rollup as follows:

$ rollup -c

If you look in bundle.js now, you’ll see it contains D3 along with our code so our bundle contains all of the code needed to run the app in one place.

It’s important to note that this only works because the D3 source code uses ES6 module syntax. Rollup doesn’t understand require statements. If you need to include other third-party libraries written in CommonJS, try adding rollup-plugin-commonjs to the plugins array; it converts CommonJS syntax to ES6 so Rollup can bundle it.

But Wait, There’s More!

The bundle we created can be further manipulated by other tools after it’s created. But we can also use Rollup as a bonafide build tool, again using plugins, applying various transforms while creating the bundle. For example, we can transpile the code from ES6 syntax or coffeescript to the ES5 equivalent, run eslint on it, or just display some useful information after each build like the bundle size. The process is the same—install the plugin and add an instance into the plugins array with some config values.

For more complex scripting Rollup can be incorporated into a build script via it’s Javascript API, or into other build systems like Grunt or Gulp.

Update: Since I first wrote this article, the folks at Rollup also created a starter app. It includes the basic plug-ins you need to get up and running as well as a few nice extras to serve up your project for development and minify the code for production.


A big thanks to @micahstubbs for proof reading this post and suggesting helpful edits.