Rollup is currently working on support for code splitting. In this post I will use this new, experimental, feature to bundle a mid size Angular application.
Code splitting is not officially released in Rollup, but this branch gives us an early look of what’s coming.
Configuration
The good news is that the configuration api is largely the same as before. The only difference is that we have to tell Rollup about our lazy loading boundaries.
Here’s the config I used for my Angular application:
import resolve from 'rollup-plugin-node-resolve';
import uglify from 'rollup-plugin-uglify';
let root = `${__dirname}`.replace('rollup', '');
const baseRxJs = `${root}node_modules/rxjs/_esm5/`;
class ResolveESM {
resolveId(importee, importer) {
if (importee.startsWith('rxjs')) {
let esm = importee.replace('rxjs/', '');
return `${baseRxJs}${esm}.js`;
}
}
}
export default {
input: ['built-es5-lazy/app-lazy/main.js',
'built-es5-lazy/app-lazy/lazy-modules/spreadsheet.module.ngfactory',
"built-es5-lazy/app-lazy/lazy-modules/dynamic-form.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/graph.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/input-controls.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/insertion-sort.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/lazy-loaded-treeview.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/pub-sub.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/redux.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/rxjs-buffering.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/rxjs-streams.module.ngfactory",
"built-es5-lazy/app-lazy/lazy-modules/text-editor.module.ngfactory"
],
output:{ dir: 'dist/rollup-lazy', format: 'cjs'},
experimentalCodeSplitting: true,
experimentalDynamicImport: true,
plugins: [
new ResolveESM(),
resolve({jsnext: true, module: true}),
uglify()
]
}
Notice, the input property is now an array, with one entry per lazy loaded module.
To make this more interesting, I’ve tested this against a mid size Angular application (11 lazy modules).
You will also notice that I have included a custom plugin for resolving rxjs to the esm distribution. This saves me a conversion from commonjs since rxjs by default is released as commonjs.
Modules
The output is one file per defined lazy module. There will also be a series of chunk files with code that is shared between the lazy modules.
One key difference when doing code splitting is that the files are not outputted as “bundles” (e.g. iife, umd). Instead the output files are modules in either es, cjs or amd format.
This means we need some sort of module loader to load the modules at runtime. In this example I have opted for commonjs as the module format, and systemjs as the module loader.
The systemjs config can be found below:
System.config({
packages: {
'lazy-modules' : {
map: {
'./spreadsheet.module.ngfactory': 'dist/rollup-lazy/spreadsheet.module.ngfactory.js',
'./text-editor.module.ngfactory': 'dist/rollup-lazy/text-editor.module.ngfactory.js',
'./rxjs-streams.module.ngfactory': 'dist/rollup-lazy/rxjs-streams.module.ngfactory.js',
'./rxjs-buffering.module.ngfactory': 'dist/rollup-lazy/rxjs-buffering.module.ngfactory.js',
'./pub-sub.module.ngfactory': 'dist/rollup-lazy/pub-sub.module.ngfactory.js',
'./lazy-loaded-treeview.module.ngfactory': 'dist/rollup-lazy/lazy-loaded-treeview.module.ngfactory.js',
'./redux.module.ngfactory': 'dist/rollup-lazy/redux.module.ngfactory.js',
'./insertion-sort.module.ngfactory': 'dist/rollup-lazy/insertion-sort.module.ngfactory.js',
'./graph.module.ngfactory': 'dist/rollup-lazy/graph.module.ngfactory.js',
'./dynamic-form.module.ngfactory': 'dist/rollup-lazy/dynamic-form.module.ngfactory.js',
'./input-controls.module.ngfactory': 'dist/rollup-lazy/input-controls.module.ngfactory.js',
'./main': 'dist/rollup-lazy/main.js'
},
defaultExtension: 'js'
}
}
});
The config is limited to the lazy modules. There is no need to load external libraries like Angular and rxjs since that is already bundled into the modules/chunks.
Demo
I have deployed the demo application here.
To provide a baseline, I have also deployed a version without code splitting here.
If you compare the two, you will see that the initial Angular bundle size is smaller for the code split version since the application is split across multiple modules.
The actual difference is 122k vs 148k in this example, but with larger components, this difference will be even more significant.