Marius Schulz
Marius Schulz
Front End Engineer

Bundling ES2015 Modules with TypeScript and Rollup

The TypeScript compiler allows you to specify the JavaScript version to transpile your code to. As of June 2016, you can target the following language levels:

  • ES3
  • ES5
  • ES6 / ES2015

Similarly, the compiler can emit modules in various formats:

  • AMD
  • CommonJS
  • ES2015
  • System
  • UMD

Depending on the JavaScript environment you're targeting, you'd choose a specific combination of language target and module format. For instance, you could pick ES6 and CommonJS when targeting Node v6.2.2, which supports pretty much all ECMAScript 2015 features except native modules.

If you're writing a web application, you'd transpile your TypeScript down to ES5 to have your JavaScript run in all current browsers. As for the module system, a popular choice is to target the CommonJS format and then use Browserify or Webpack to bundle all modules together into a single file.

#Bundling ES2015 Modules with Rollup

In addition to Browserify and Webpack, there's a new kid on the block: Rollup, the next-generation JavaScript module bundler. Its main value proposition is tree-shaking, a process that automatically excludes unused module exports from bundles. The idea is that there's no need to include all functions of a library in the generated bundle if your application only imports a few of them.

Rollup needs to understand the entire dependency graph of your modules in order to determine which exports are being used by your application. The fully static structure of the ECMAScript 2015 module system makes it possible to analyze all imports and exports at compile-time. Check out Bundling and Tree-Shaking with Rollup and ECMAScript 2015 Modules for more details.

#Emitting ES2015 Modules and ES5 Code with tsc

To create a web application that runs in all browsers, the TypeScript compiler must target ES3 or ES5. At the same time, it needs to emit ES2015 modules so that Rollup can do its work. Up until recently, these were conflicting requirements that made the compiler complain. You could only emit ES2015 modules when targeting ES6:

Cannot compile modules into 'es2015' when targeting 'ES5' or lower

This restriction has been removed with pull request #9042, which has been merged into the master branch. The feature will be part of the upcoming TypeScript 2.0 release and is available in the nightly builds today.

#Creating a Bundle with TypeScript and Rollup

Let's take look at an example. Here's a simple math module that exports two functions using the exponentiation operator standardized in ECMAScript 2016:

// math.ts
export function square(x: number) {
  return x ** 2;
}

export function cube(x: number) {
  return x ** 3;
}

The square function is then imported in the main module:

// main.ts
import { square } from "./math";

console.log(square(3));

I'm using a nightly build of the TypeScript compiler with the following options in the tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015"
  }
}

Here's the math.js file with all traces of TypeScript removed. Except for the export keyword, it's valid ES5 code:

// math.js
export function square(x) {
  return Math.pow(x, 2);
}
export function cube(x) {
  return Math.pow(x, 3);
}

Except for a missing blank line, the main.js is no different from the original TypeScript file:

// main.js
import { square } from "./math";
console.log(square(3));

If we now run the rollup main.js command, we get the following bundle:

function square(x) {
  return Math.pow(x, 2);
}

console.log(square(3));

Notice what just happened: Rollup determined that the exported cube function is never used, so it's not part of the bundle. Also, all import and export keywords are gone because all dependencies have been inlined in the correct order.

And there it is, the entire application in a single file containing only ES5 code. No TypeScript, no ECMAScript 2015 modules!