Bundle Angular ESM2015 With Rollup

- Developer and Blogger
Published: Sat Dec 30 2017

Today I will test if there is a noticeable change in Angular bundle size from using ESM2015 bundles vs ESM5 bundles when using Rollup.

In case you are wondering about terminology, ESM2015 is a pure ES2015 (ES6) distribution of Angular.

ESM5 is a hybrid format consisting mostly of regular ES5 JavaScript, but with import/export statements added as the only ES2015 feature.


ESM5 is practical for Rollup bundling since it satisfies Rollup’s ES2015 module requirement. Another benefit is that there is no need for down-leveling to ES5 since the only ES2015 feature (import/export statements) falls out during bundling.

By “falling out”, I mean the import/export statements themselves are not bundled into the final bundle.

ESM2015 will of course also satisfy Rollup’s ES2015 module requirement, but down-leveling will typically be required if you target older browsers.

Due to the simplicity of using ESM5 with Rollup I have generally opted for this solution. I really like that you can define the whole bundling process in a single rollup-config file. Basically everything from node resolution via “module” in package.json to minification via the Uglify plugin can be defined in one place.


In this experiment, all application code + Angular framework code will be included as ES2015 JavaScript.

Targeting the ESM2015 bundles is slightly more complex. First we have to redirect Rollup to the folder where the ES2015 bundles are located. There is a new field in package.json called es2015, but I don’t think the nodeResolve plugin is aware of this setting.

Instead I opted to write a simple Rollup plugin to include the correct bundles.

I have included my entire config with the custom ResolveFESM2015 plugin below:

import nodeResolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; class ResolveFESM2015 { resolveId(importee, importer) { if(importee.startsWith('@angular')) { const pkg = importee.replace('@angular', ''); return `node_modules/${importee}/esm2015/${pkg}.js`; } } } export default { input: 'built-es6/app/main.js', output: {file: 'dist/bundle-rollup-es6.js', format: 'iife'}, sourceMap: false, plugins: [ new ResolveFESM2015(), commonjs({ include: 'node_modules/rxjs/**' }), nodeResolve({ jsnext: true, module: true }) ] }

The plugin is pretty basic. It boils down to intercepting imports of anything @angular, and redirect Rollup to the corresponding file under the es2015 folder.

One note: It appears that the ResolveFESM2015 plugin has to be listed first in the list of plugins for this to work.

After running Rollup against this config the Angular bits have been included as pure ES2015.

Where did the Uglify plugin go?

ES2015 JavaScript is not compatible with the Uglify plugin since Uglify only works with ES5.

This means we need to add down-leveling outside of Rollup. In this case I will actually use the TypeScript compiler to convert ES2015 to ES5. This is possible with the –allowJs flag in combination with target=es5.

Here is the command:

tsc dist/bundle-rollup-es6.js --target es5 --allowJS --outFile dist/bundle-rollup-es5.js

Running tsc gives us a new ES5 bundle.

Next we have to run Uglify on the ES5 bundle.

We can do this using UglifyJS directly.

Here is the command:

uglifyjs dist/bundle-rollup-es5.js -o dist/bundle-rollup-es5.min.js -c -m

You can of course fine tune the parameters to fit your needs, but I think this is pretty standard.

Comparing Bundle Sizes

The goal of this exercise was to see if there is a noticeable size difference between the two approaches…. For this experiment the difference was measurable but pretty minor. The regular ESM5 bundle was roughly 1k bigger than the ESM2015 based bundle.

You can compare the two versions here:


Even though 1k is not huge, the difference might be more noticable in a larger project. Here we are dealing with a relatively small application (149k vs 148k).

It’s a bit tricky to determine if the size difference is a result of better Rollup performance against pure ES2015. Especially since there are two other external processes at play here now (TypeScript + Uglify).

It's still an interesting experiment. In this particular application the gain is not worth the added complexity, but other applications might benefit more.

You can find the code here (npm run rollup-demo vs npm run rollup-es6).

I invite you to follow me on twitter