By the end of this tutorial, we’ll have Rollup configured to:
- combine our scripts,
- remove unused code,
- transpile it to work with older browsers,
- support the use of Node modules in the browser,
- work with environment variables, and
- compress and minify our code for the smallest possible file size.
- Initial familiarity with ES2015 modules doesn’t hurt, either.
- You’ll need
npminstalled on your machine. (Don’t have it? Install Node.js here.)
- Part II: How to Use Rollup to Process and Bundle Stylesheets
What Is Rollup?
In their own words:
Why should you care about Rollup?
This happens because Rollup is based on ES2015 modules, which are more efficient than CommonJS modules, which are what webpack and Browserify use. It’s also much easier for Rollup to remove unused code from modules using something called tree-shaking, which basically just means only the code we actually need is included in the final bundle.
Tree-shaking becomes really important when we’re including third-party tools or frameworks that have dozens of functions and methods available. If we’re only using one or two — think lodash or jQuery — there’s a lot of wasted overhead in loading the rest of the library.
Browserify and webpack will end up including a lot of unused code right now. But Rollup doesn’t — it’ll only bring in what we’re actually using.
And that’s huge.
In order to get started, we need to have some code to work with. For this tutorial, we’ll be working with a small app, available on GitHub.
The folder structure looks like this:
You can install the app we’ll be working with during this tutorial by running the following command into your terminal.
Step 1: Install Rollup and create a configuration file.
To get started, install Rollup with the following command:
Next, create a new file called
rollup.config.js in the
learn-rollup folder. Inside, add the following.
Let’s talk about what each of these configuration options actually does:
dest— this is the location where the processed scripts should be saved.
format— Rollup supports several output formats. Since we’re running in the browser, we want to use an immediately-invoked function expression (IIFE).
(This is a fairly complex concept to understand, so don’t stress if it doesn’t make total sense. In a nutshell, we want our code to be inside its own scope, which prevents conflicts with other scripts. An IIFE is a closure that contains our code in its own scope.)
sourceMap— it’s extremely helpful for debugging to provide a sourcemap. This option adds a sourcemap inside the generated file, which keeps things simple.
Test the Rollup configuration.
Once we’ve created the config file, we can test that everything is working by running the following command in our terminal:
This will create a new folder called
build in your project, with a
js subfolder that contains our generated
We can see that the bundle was created properly by opening
build/index.html in our browser:
Look at the Bundled Output
What makes Rollup powerful is the fact that it uses “tree-shaking”, which leaves out unused code in the modules we reference. For example, in
src/scripts/modules/mod1.js, there’s a function called
sayGoodbyeTo() that isn’t used in our app — and since it’s never used, Rollup doesn’t include it in our bundle:
In other build tools that’s not always the case, and bundles can get really large if we include everything inside a bigger library like lodash just to reference one or two functions.
For example, using webpack, the
sayGoodbyeTo() function is included, and the resulting bundle is more than double the size of what Rollup generates.
At this point, we’ve got a code bundle that will work in modern browsers, but it’ll break if the browser is even a couple versions old in some cases — that’s not ideal.
So let’s make that part of our Rollup process so we don’t have to think about it.
Install the necessary modules.
Next, create a new file called
.babelrc in your project’s root directory (
learn-rollup/). Inside, add the following JSON:
This tells Babel which preset it should use during transpiling.
To make this actually do stuff, we need to update
import the Babel plugin, then add it to a new configuration property called
plugins, which will hold an array of plugins.
In order to avoid transpiling third-party scripts, we set an
exclude config property to ignore the
Check the bundle output.
With everything installed and configured, we can rebuild the bundle:
When we look at the output, it looks mostly the same. But there are a few key differences: for example, look at the
See how Babel converted the fat-arrow function (
arr.reduce((a, b) => a + b, 0))to a regular function?
That’s transpiling in action: the result is the same, but the code is now supported back to IE9.
It’s always a good idea to use a linter for your code, because it enforces consistent coding practices and helps catch tricky bugs like missing brackets or parentheses.
For this project, we’ll be using ESLint.
Install the Modules.
In order to use ESLint, we’ll want to install the ESLint Rollup plugin:
To make sure we only get errors we want, we need to configure ESLint first. Fortunately, we can automatically generate most of this configuration by running the following command:
If you answer the questions as shown above, you’ll get the following output in
However, we need to make a couple adjustments to avoid errors for our project:
- We’re using 2 spaces instead of 4.
- We will use a global variable called
ENVlater, so we need to whitelist that.
Make the following adjustments — the
globals property and the adjustment to the
indent property — to your
import the ESLint plugin and add it to the Rollup configuration:
Check the console output.
At first, when we run
./node_modules/.bin/rollup -c, nothing seems to be happening. That’s because as it stands, the app’s code passes the linter without issues.
But if we introduce an issue — say removing a semicolon — we’ll see how ESLint helps:
Something that has the potential to introduce a mystery bug is now pointed out instantly, including the file, line, and column where the issue is happening.
While this won’t eliminate all of our problems with debugging, it goes a long way toward squashing bugs that are due to obvious typos and oversights.
(As someone who has previously spent — ahem — numerous hours chasing bugs that ended up being something as silly as a misspelled variable name, it’s hard to overstate the efficiency boost that working with a linter provides.)
Step 4: Add plugins to handle non-ES modules.
This is important if any of your dependencies use Node-style modules. Without it, you’ll get errors about
Add a Node module as a dependency.
It would be easy to bang through this sample project without referencing a third-party module, but that’s not going to cut it in real projects. So, in the interest of making our Rollup setup actually useful, let’s make sure we can also reference third-party modules in our code.
For simplicity, we’ll add a simple logger to our code using the
debug package. Start by installing it:
src/scripts/main.js, let’s add some simple logging:
So far so good, but when we run rollup we get a warning:
And if we check our
index.html again, we can see that a
ReferenceError was thrown for
Well, shit. That didn’t work at all.
This happens because Node modules use CommonJS, which isn’t compatible with Rollup out of the box. To solve this, we need to add a couple plugins for handling Node dependencies and CommonJS modules.
Install the modules.
To work around this problem, we’re going to add two plugins to Rollup:
rollup-plugin-node-resolve, which allows us to load third-party modules in
rollup-plugin-commonjs, which coverts CommonJS modules to ES6, which stops them from breaking Rollup.
Install both plugins with the following command:
import and add the plugins to the Rollup config:
Check the console output.
Rebuild the bundle with
./node_modules/.bin/rollup -c, then check the browser again to see the output:
Step 5: Add a plugin to replace environment variables.
Environment variables add a lot of power to our development flow, and give us the ability to do things such as turning logging off and on, injecting dev-only scripts, and more.
So let’s make sure Rollup will enable us to use them.
ENV-based conditional in
Let’s make use of an environment variable and only enable our logging script if we’re not in
production mode. In
src/scripts/main.js, let’s change the way our
log() is initialized:
However, after we rebuild our bundle (
./node_modules/.bin/rollup -c) and check the browser, we can see that this gives us a
That shouldn’t be surprising, though, because we haven’t defined it anywhere. But if we try something like
ENV=production ./node_modules/.bin/rollup -c, it still doesn’t work. This is because setting an environment variable that way only makes it available to Rollup, not to the bundle created by Rollup.
We’ll need to use a plugin to pass our environment variables into the bundle.
Install the modules.
Start by installing
rollup-plugin-replace, which is essentially just a find-and-replace utility. It can do a lot of things, but for our purposes we’ll have it simply find an occurrence of an environment variable and replace it with the actual value (e.g. all occurrences of
ENV would be replaced with
"production" in the bundle).
import the plugin and add it to our list of plugins.
The configuration is pretty straightforward: we can just add a list of
key: value pairs, where the
key is the string to replace, and the
value is what it should be replaced with.
In our configuration, we’re going to find every occurence of
ENV and replace it with either the value of
process.env.NODE_ENV — the conventional way of setting the environment in Node apps — or “development”. We use
JSON.stringify() to make sure the value is wrapped in double quotes, since
ENV is not.
To make sure we don’t cause issues with third-party code, we also set the
exclude property to ignore our
node_modules directory and all the packages it contains. (Thanks to @wesleycoder for the heads-up on this.)
Check the results.
To start, rebuild the bundle and check the browser. The console log should show up, just like before. That’s good — that means our default value was applied.
To see where the power comes in, let’s run the command in
When we reload the browser, there’s nothing logged to the console:
Step 6: Add UglifyJS to compress and minify our generated script.
Install the plugin.
Install it with the following:
Next, let’s add Uglify to our Rollup config. However, for legibility during development, let’s make uglification a production-only feature:
We’re using something called short-circuit evaluation, which is a common (though debatably evil) shortcut for conditionally setting a value. (For example, it’s pretty common to see this used to assign default values, like
var value = maybeThisExists || 'default'.)
In our case, we’re only loading
NODE_ENV is set to “production”.
Check the minified bundle.
With the configuration saved, let’s run Rollup with
NODE_ENV in production:
The output isn’t pretty, but it’s much smaller. Here’s a screenshot of what
build/js/main.min.js looks like now:
Before, our bundle was ~42KB. After running it through UglifyJS, it’s down to ~29KB — we just saved over 30% on file size with no additional effort.
Coming Up Next
In the next installment of this series, we’ll look at handling stylesheets through Rollup using PostCSS, as well as adding live reloading so we can see our changes near-instantaneously as we make them.