Codementor Events

How to Bundle Angular 2 with Rollup

Published Sep 06, 2016Last updated Jan 18, 2017
How to Bundle Angular 2 with Rollup

Tools like Browserify and Webpack have been fairly popular among UI engineers to bundle their source code. Rollup is fairly new—and honestly a breath of fresh air after moving my Angular 2 source code from Alpha to RC. Along the way I tried every option to make a stable development and production environment from using Gulp, SystemJS, JSPM, Angular CLI and then Webpack, and now Rollup.

In this article, I will share my findings and walk you through the start code project I built. Hopefully, others can see the benefits of bundling with Rollup.

Clone the angular2-rollup repository to get the code described in this article. Follow the instructions in the README of this repository for getting the development environment up and running. The optimized bundle that Rollup creates may negate the need for lazy loading; depending on the scale of your application.

Rollup Has Many Advantages

I could digress into the pros and cons of all these options, but that is probably another article in itself. This post focuses on getting up and running with Rollup. But first, let's take a moment to focus on some of the advantages of Rollup over the competition.

  • The bundle is easier to read than Browserify and Webpack, which comes in handy for debugging in the browser
  • Rollup uses a process called Tree Shaking, resulting in a highly optimized bundle
  • Configure the bundle with minimal code written in ES2015
  • Development environment is close to mirroring production environment
  • The Angular 2 Rollup plugin supports inlining CSS and HTML

There Are Some Disadvantages

  • ngModule cannot be lazy loaded out of the box
  • Shortage of documentation

So far Rollup doesn't support code splitting, which is needed to leverage the lazy loading ngModule found in Angular 2 RC5 and above. As of writing this article, the issue is open on the Rollup Github repository. We may see this feature in a later release of Rollup or possibly via a plugin built by the development community.

Shake That Tree

The biggest advantage to using Rollup versus the competition is tree-shaking. Tree-shaking means excluding unused parts of your codebase from a bundle. Tree-shaking only works with ES2015 Modules because the content is static. ES5 Modules often have shifting APIs and content, making them really hard to tree shake. As long as code is written in ES2015, it can be tree shaken by Rollup and all the unused portions of the codebase get dropped from the bundle. For a production environment, you can also use tools like Uglify to minify and optimize the bundle even more. @angular/compiler-cli also provides a way to precompile your application instead of in the browser, which results in an even smaller bundle because now you don't have to package the compiler with your code. You can learn more about ngc here.

Bundle It!

Rollup creates highly optimized bundles through the process of tree-shaking.

TS/ES2015 => Tree Shaking => ES2015 Bundle 

We can then use any tool to transpile the ES2015 bundle. A lot of Angular 2 engineers use Typescript to transpile, so let's add it to the process:

TS/ES2015 => Tree Shaking => ES2015 Bundle => tsc => ES5 / ES3

This process results in a highly optimized bundle. When I uglified the code that Typescript transpiled to ES5, the file size shrunk another 50%. gzipping the minified bundle resulted in a 28kb file! It's amazing to think this application could get even smaller by introducing Angular-compiler.

Rollup Bundle 310kb => Uglify 135kb => gzip 28kb

Results may vary for your source code, but I wouldn't be surprised if you see a drop in file size for the bundle versus Webpack.

How To Get Started With Rollup

In this example, we are going to separate the Rollup config into two files: one for vendor code and another for Angular 2 application source. The result of running rollup in the command line will be two bundles: vendor.js and bundle.js.

Building Vendor Code

Here is what the vendor config file looks like. This file should be placed in the project's Root directory. An explanation is below.

// rollup.config.vendor.js
import alias from 'rollup-plugin-alias';
import typescript from 'rollup-plugin-typescript';
import resolve from 'rollup-plugin-node-resolve';

export default {
 entry: 'src/vendor.ts',
 dest: 'dist/vendor.es2015.js',
 format: 'iife',
 moduleName: 'vendor',
 plugins: [
   typescript(),
   alias({ rxjs: __dirname + '/node_modules/rxjs-es' }),
   resolve({ jsnext: true,
             main: true,
             browser: true }),
 ]
}

In the first few lines of rollup.config.vendor.js we are importing three plugins that are installed via npm. Rollup relies on a developer community to make plugins.

  • rollup-plugin-alias allows you to alias modules from one module pattern to another. It is one way to deal with the problem of referencing ES5 code in your TS or ES6 code. alias() is called in the plugins Array later in the file. We are using it to alias the ES2015 version of RxJS to all the references of RxJS in our project.
  • rollup-plugin-typescript plugin allows Rollup to parse Typescript code.
  • rollup-plugin-node-resolve allows Rollup to locate modules using the Node resolution algorithm, for using third party modules in node_modules.

Notice the config exports on an Object which contains the actual configuration. This Object has three essential properties:

 entry: 'src/vendor.ts',
 dest: 'dist/vendor.es2015.js',
 format: 'iife'

entry is equal to a String that represents the path to the top most entry point of our tree of code we want Rollup to compile into a bundle. The entry point of this vendor config is actually flat. But when we write the config for our application source code, the entry point will be the .ts file that bootstraps our application, which is at the top of a tree of Components.

dest is the path to the directory where we want the bundle to compile to along with the filename of the ES2015 bundle that will be created. The file will get picked up by tsc and transpiled down to ES5 by Typescript. The vendor.es2015.js can be treated as a temporary file and should probably be.gitignored.

The format of the bundle, in this case will be an Immediately Invoked Function Expression or iife.

IIFE

Using a IIFE has several advantages and some side effects when Rollup can't find a dependency that is referenced in your source code. Rollup will assume the dependency and is on the window, then inject it into the IIFE. This is just one reason to be explicit when defining your dependencies with Rollup, but also a lifesaver if we have to hard code a vendor dependency in a <script> tag the <head> for some reason.

Entry Point

In the actual vendor.ts file used as an entry point for vendor.es2015.js, we see that ES2015 dependencies common to Angular 2 development get imported and exported.

// vendor.ts
import * as _angular_common from '@angular/common';
import * as _angular_compiler from '@angular/compiler';
import * as _angular_core from '@angular/core';
import * as _angular_http from '@angular/http';
import * as _angular_platformBrowser from '@angular/platform-browser';
import * as _angular_platformBrowserDynamic from '@angular/platform-browser-dynamic';
import * as _angular_router from '@angular/router';
import * as _angular_forms from '@angular/forms';

export default {
  _angular_common,
  _angular_compiler,
  _angular_core,
  _angular_http,
  _angular_platformBrowser,
  _angular_platformBrowserDynamic,
  _angular_router,
  _angular_forms
};

To make Rollup create the bundle, run the following in the command line:

rollup -c rollup.config.vendor.js

Then use Typescript to transpile the bundle from ES2015 to ES5 for the browser.

tsc --out ./dist/vendor.js --target es5 --allowJs dist/vendor.es2015.js 

Now you should see a vendor.ts file in the /dist folder.

NOTE: The repository has several shortcuts for building the starter code, take a look in the package.json for all the CLI commands.

Bundling the App Source Code

rollup.config.ts is the configuration for bundling source code found in the /src folder. The format of this file is the same as rollup.config.vendor.ts with a few additional parameters on the exported Object.

//rollup.config.ts

import alias from 'rollup-plugin-alias';
import resolve from 'rollup-plugin-node-resolve';
import typescript from 'rollup-plugin-typescript';
import angular from 'rollup-plugin-angular';

export default {
  entry: 'src/main.ts',
  format: 'iife',
  dest: 'dist/bundle.es2015.js',
  sourceMap: true,
  plugins: [
    angular(),
    typescript(),
    alias({ rxjs: __dirname + '/node_modules/rxjs-es' }),
    resolve({ jsnext: true,
              main: true,
              browser: true })
  ],
  external: [
    '@angular/core',
    '@angular/common',
    '@angular/compiler',
    '@angular/core',
    '@angular/http',
    '@angular/platform-browser',
    '@angular/platform-browser-dynamic',
    '@angular/router',
    '@angular/router-deprecated'
  ],
  globals: {
    '@angular/common' : 'vendor._angular_common',
    '@angular/compiler' : 'vendor._angular_compiler',
    '@angular/core' : 'vendor._angular_core',
    '@angular/http' : 'vendor._angular_http',
    '@angular/platform-browser' : 'vendor._angular_platformBrowser',
    '@angular/platform-browser-dynamic' : 'vendor._angular_platformBrowserDynamic',
    '@angular/router' : 'vendor._angular_router',
    '@angular/forms' : 'vendor._angular_forms'
  }
}

We added the rollup-plugin-angular plugin to this config which handles inlining all .html and .css in the @Component in our Angular 2 source code. This means in each @Component, you don't have to be coerced into includingmoduleId: module.id` like with SystemJS.

We also added two new properties to a Rollup config: external and globals. Both are Arrays of Strings. external is a list of all vendor modules, while globals maps the module names to where the dependency is found via the properties of vendor.js.

The entry point for this config (src/main.ts) looks like a typical call to bootstrap, as the syntax is written in RC5 at the time of writing this post.

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { enableProdMode } from '@angular/core';

platformBrowserDynamic().bootstrapModule(AppModule)
                        .catch(err => console.error(err));

For this configuration, let's combine the rollup and tsc commands.

rollup -c rollup.config.js && tsc --out ./dist/bundle.js --target es5 --allowJs dist/bundle.es2015.js

When Rollup and tsc run, bundle.js will be output to the /dist directory.

To handle other application files, all you need is a little organization. One convention is to use a /public directory. I wrote a short npm script that copies all files from /src/public to /dist. One of the files that gets copied to the Root of /dist from /src/public is index.html.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <base href="/">

    <title>App</title>

    <link rel="icon" type="image/x-icon" href="/img/favicon.ico">

    <script src="/lib/es6-shim/es6-shim.js"></script>
    <script src="/lib/zone.js/dist/zone.js"></script>
    <script src="/lib/reflect-metadata/Reflect.js"></script>

  </head>
  <body>

    <app-root>
        Loading...
    </app-root>

    <script src="/vendor.js"></script>
    <script src="/bundle.js"></script>

  </body>
</html>

Notice how Reflect.js and Zone.js are included in <script> tags. You could put other vendor dependencies here as well, but the preferred method would be to bundle them with the vendor bundle created above. The two bundles vendor.js and bundle.js are included before the closing <body> tag.

Viewing the Build

You can run a command like live-server in the /dist directory to serve your web application for the browser.

To keep Angular 2 running with PathLocationStrategy, run live-server like so:

live-server --entry-file=index.html --port=4200

Navigate to http://localhost:4200 in your browser to view the application.

The --entry-file option tells the http server to serve index.htmlin place of missing files that would otherwise 404. It allows Angular 2 to be a Single Page Application without the need for HashLocationStrategy.

live-server will watch for changes in the /dist folder. When the bundles change or CSS gets compiled, or files get copied from /src/public, the live-server should refresh the browser window.

Conclusion

Rollup is an excellent alternative to Webpack, SystemJS, or Browserify for bundling Angular 2 web applications. Not only does Rollup provide optimized bundles, but the process outlined in this article also provides a development environment that mirrors production closer than other bundlers. The process of bundling is actually quite fast as well, but may slow down as an application scales. With some experimenting you may find Rollup is a great fit for your next web application built with Angular 2.

Other tutorials you might find interesting:


Can't Get it Working?

Contact Steve Belovarich on Codementor to set up a session where I walk you through the example repo and help you move your Angular 2 project over to Rollup!

Discover and read more posts from Steve Belovarich
get started
post commentsBe the first to share your opinion
Steve Belovarich
7 years ago

This post is outdated. It was from Angular 2 Beta when AOT compile was not finalized and tree shaking in Angular was experimental. Codementor does not allow editing posts after publish otherwise I’d put a disclaimer at the top. If you want my latest recommendation for bundling with Rollup check out this repository that can be used as starter code. https://github.com/steveblu… I’ll be attending ng-conf this year if anyone wants to chat about Rollup in person. Regards, Steve

Max Harris
7 years ago

This seems like it would remove the benefit of tree shaking from your dependencies/vendor? Is that true since everything is just being bundled separately?

Chris
7 years ago

Any luck getting server side rendering going with a rollup-only build?

Show more replies