Understanding Rollup.js: A Beginner’s Guide

Peek inside a modern JavaScript project and you will probably see a folder called dist with some massive files inside it with names like colorful.min.js, colorful.esm.js, colorful.umd.js etc. Each of them is meant for a specific target and contain the entire optimized code of your project.

Where did these files come from? A bundler creates them. There are quite a few bundlers that you can use. However, our focus in this post will be on Rollup.

Rollup is a module bundler. A quiet but powerful tool that takes all your scattered JavaScript files, follows every import trail, removes whatever you’re not using, and rolls it all up into one clean file that browsers can actually understand. It’s the reason your dozens of .js files can load as a single, efficient script on the web.

Why Use a Bundler at All?

In the early days of the web, you could drop one JavaScript file into your page with a simple line like this:

<script src="app.js"></script>

That worked fine when your entire project was a few hundred lines of code.

But as apps grew, developers began splitting code into multiple files like math.js, utils.js, api.js, and so on to keep things organized. The browser, however, doesn’t natively understand your project structure. It just sees separate files that it has to fetch one by one.

Each <script> tag means a separate network request:

<script src="math.js"></script>
<script src="utils.js"></script>
<script src="main.js"></script>

That’s three requests for three files.

Now imagine a modern app with hundreds of files — that’s hundreds of separate downloads before your app can even start. Slow, messy, and frustrating.

A bundler solves this. It takes all those small files, figures out how they depend on each other, and merges them into one or a few optimized files that the browser can load instantly. It also removes unused code (a process called tree-shaking), minifies everything, and can even make your modern syntax work on older browsers.

How Rollup Works (The Simple Version)

When you give Rollup an entry file, say src/colorful.js, it starts reading your code. Every time it sees an import statement, it follows the trail, pulling in the imported files. It repeats this process recursively until it has your entire project mapped out like a dependency web.

Then, it rolls everything together:

  1. Combines all modules into one file
  2. Removes anything you didn’t use
  3. Produces clean, optimized output files like:
    colorful.esm.js (for modern JavaScript)
    colorful.cjs.js (for Node.js)
    colorful.umd.js (for browsers and AMD)
    colorful.min.js (a minified version for production)

That’s the “roll up” in Rollup — a bundle of everything you need, nothing you don’t.

Multiple Output Formats Explained

When you open your dist folder after a Rollup build, you might see several files that look suspiciously similar:

colorful.esm.js  
colorful.cjs.js  
colorful.umd.js  
colorful.min.js

They all contain the same codebase, your project, but each one speaks a slightly different “dialect” of JavaScript. Think of them as the same book translated for different readers.

Let’s break down what each format means.

ESM — For the Modern World

ESM stands for ES Modules, the official JavaScript module system introduced in ES6 (import and export).

This format is used by modern browsers and tools like Rollup, Vite, and Webpack.

You can use it like this:

import { getRandomColor } from './colorful.esm.js';

Because ESM supports static imports, bundlers can tree-shake it efficiently — meaning unused parts of your library simply vanish in the final build. This is the most modern and future-proof format.

CJS — The Node.js Veteran

CJS stands for CommonJS, the older module system used by Node.js. It uses require() and module.exports instead of import and export:

const { getRandomColor } = require('./colorful.cjs.js');

This format is mostly for backward compatibility. Older tools, servers, and Node environments still rely on CommonJS. If you’re publishing a library on npm, you’ll definitely want to include this.

UMD — The Universal Translator

UMD stands for Universal Module Definition, and it’s like a multilingual interpreter. It works everywhere — Node, AMD (like RequireJS), or directly in the browser.

You can even drop it into an HTML file:

<script src="colorful.umd.js"></script>
<script>
  console.log(Colorful.getRandomColor());
</script>

It attaches your code to a global variable (in this case, Colorful), so browsers without module support can still use it. This makes UMD great for demos, playgrounds, or older environments.

IIFE — The Browser-Ready File

IIFE stands for Immediately Invoked Function Expression, a self-running script. When loaded, it runs instantly and exposes your library as a global variable.

<script src="colorful.min.js"></script>
<script>
  console.log(Colorful.getRandomColor());
</script>

The .min.js version is the same code, just minified with Terser to make it smaller and faster to load.

So… Why So Many?

Because JavaScript runs in a lot of different environments like browsers, servers, build tools, and everything in between.

Rollup makes it easy to build once and ship everywhere.

Getting Started with Rollup

Let’s start from scratch so you know how Rollup fits in a project.

Execute the following commands in the terminal to create a math-demo directory and a package.json file within it.

mkdir math-demo
cd math-demo
npm init -y

Install Rollup

Now install Rollup and some of its most common plugins by executing the following command.

npm install rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-terser --save-dev
  • rollup: the core bundler
  • node-resolve: lets Rollup import packages from node_modules
  • commonjs: converts old-style CommonJS modules into ES modules
  • terser: minifies your final output

Write Some Code

Now create the src directory within your project directory, i.e., math-demo, and create two files named math.js and main.js within the src directory.

src/
  math.js
  main.js

Add the following code to math.js:

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

Add the following code to main.js:

import { add } from './math.js';

console.log('2 + 3 =', add(2, 3));

You now have a mini “project” with one file importing another. This is perfect for testing a bundler.

Create a Rollup Config

Add a file named rollup.config.js in your root folder, i.e., math-demo with the following code:

import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';

export default {
  input: 'src/main.js',           // Entry point
  output: {
    file: 'dist/math-demo.min.js',   // Output file
    format: 'iife',               // IIFE for browsers
    name: 'MathDemoApp',          // Global variable name
    plugins: [terser()]           // Minify output
  },
  plugins: [nodeResolve(), commonjs()]
};

This tells Rollup:

Start from main.js, gather everything it imports, and create a single file at dist/math-demo.min.js.

Run the build

Execute the following command in your terminal now:

npx rollup -c

and you should see:

dist/
  math-demo.min.js

This file should have the following code:

!function(){"use strict";console.log("2 + 3 =",2+3)}();

What happened? Where are add() and multiply()?

Understanding What Happened

Rollup started at main.js and built a dependency graph.
It noticed:

  • You imported only add.
  • You never used multiply.

So during bundling, Rollup did tree-shaking and removed multiply() completely and gave us this code:

(function () {
  'use strict';

  // src/math.js
  function add(a, b) {
    return a + b;
  }

  // src/main.js
  console.log('2 + 3 =', add(2, 3));

})();

Now it was Terser’s turn to do its magic and it does the following:

  1. Remove whitespace and comments.
  2. Rename variables (if safe).
  3. Inline trivial functions (this is what you noticed).
  4. Remove redundant return paths or scopes.

Since your add() was:

function add(a, b) { return a + b; }

and it was only used once, Terser said:

“No point keeping this function. I’ll just replace add(2, 3) with 2 + 3.”

The end result was this code:

!function(){"use strict";console.log("2 + 3 =",2+3)}();

You could replace the original output value in rollup.config.js with this one to create three different production ready files:

output: [
    {
      file: 'dist/math-demo.esm.js',
      format: 'esm',
    },
    {
      file: 'dist/math-demo.min.js',
      format: 'iife',
      name: 'MathApp',
      plugins: [terser()]
    },
    {
      file: 'dist/math-demo.js',
      format: 'iife',
      name: 'MathApp'
    }
  ],

Final Thoughts

Bundling may look like magic at first. One moment you have a folder full of small files, and the next you’ve got a single bundle.js ready to run anywhere. But as you’ve seen, it’s just smart, systematic work: Rollup follows every import, removes the extras, and builds one efficient file for your browser, Node, or any environment you choose.

Rollup stands out because it does this job with minimal setup and maximum clarity. You don’t need a monster config or a web of plugins to get started just input, output, format, and plugins. Whether you’re creating a small utility library or learning how build tools actually work, Rollup gives you a front-row seat to how modern JavaScript comes together behind the scenes.

CategoriesDev Notes

Leave a Comment

Your email address will not be published.

0%