Stack update 2016: Future-proofing our javascript bundling with webpack 2 and rollup

Stack update 2016: Future-proofing our javascript bundling with webpack 2 and rollup

Since Aguinaga’s popular post “How it feels to learn JavaScript in 2016” the term “javascript fatigue” is discussed regularly, describing the constant pressure to re-learn javascript libraries and tools because of the sudden availability of better, shinier options.

I don’t think that this is particularly problematic. Your primary focus as a developer is to produce decent software while using an acceptable (not perfect) stack of technologies. A regular re-evaluation of used base technologies is healthy and fun, though. Let’s start out with managing and bundling javascript:

Static module loading and bundling

Tl;dr: Code in ES6, use ES6 module syntax and use webpack 2 for complex apps, rollup for libraries and simpler js.

Our old approach: Webpack 2

Managing dependencies is a complex task that bundlers like browserify, webpack and rollup aim to solve. A single react component for example could depend on many individual packages.

Since javascript engines don’t have a concept of javascript modules yet (as the actual loading of modules is still dependent on the unfinished whatwg loader specification) bundlers generally mash everything into one or two javascript files that can be executed normally. In big javascript apps this would mean that possibly hundreds of modules get loaded in one file, even if they are not used in the current context.

Webpack always stood out with a great all-in one experience and the possibility to create web applications with multiple entry points and use code splitting to create common dependencies which can be cached and reused.

Example: Saving bandwidth with Multiple Entry Points

As a short example a webapp with two sections to illustrate when this is useful:

  • Section A (http://example.com/visualization/*): Has a 3D animation embedded in a react component.
  • Section B (http://example.com/statistics/*): Has a graphing library in a react component

Unoptimized: So traditionally we would mash everything in a single bundle / file:

  • Section A: bundle.js (React, Utility, Three.js, Chart.js) (800k)
  • Section B: bundle.js (React, Utility, Three.js, Chart.js) (800k) This means that by visiting either section, the whole javascript is downloaded and initialized, no worries there, but a huge file download initially.

Optimized: With multiple entry points this can be split more intelligently:

  • Section A: base-bundle.js (React, Utility) (200k) + three-bundle.js (Three.js) (400k) = 600k
  • Section B: base-bundle.js (React, Utility) (200k) + chart-bundle.js (Chart.js) (200k) = 400k

So if visiting only section B you would only need to load half the javascript. In more complex examples this would even be more noticeable.

Our updated approach: Webpack 2 + Rollup

The new module builder Rollup uses ES6 natively and has many advantages. A comparison with the ES6 capabilities of webpack 2 is outlined in this comment from Rollups creator Richard Harris. The main advantages are:

  • Rollup uses ES6 module syntax natively, which get’s you a cleaner syntax than commonjs and further future-proofing.
  • It produces smaller bundles
  • It provides us with the option to specify an own ES6 entry point in NPM packages which is extremely useful if you want to reuse your own code by packaging it. You will still have to build it, though, since there has to be a common set of advanced ES6 features across packages.

As Richard states: Rollup is by far the best choice for libraries, as it provides us with true ES6 interoperability and smaller bundles in complex scenarios. In complex applications webpack 2 still has the edge with advanced features like code splitting and while webpacks ES6 features don’t reach as deep as Rollup’s, it rarely makes a significant difference in production.

We now use both technologies: Rollup as our build tool of choice for library-like packages and scenarios where the javascript assets are handled very traditionally (for example in drupal themes) and webpack for our react and other client-side heavy apps. If code splitting support lands for rollup, this could change.

Dynamically Loading Modules

Tl;dr: Use webpack 2 and its “System.import”. You probably won’t need it in simpler applications.

In an ideal world all packages would provide ES6 syntax, our browsers would resolve and load those packages dynamically and we could forego using rollup altogether, but looking at the state of the implementations there is still a long way to go.

In the meantime one could use SystemJS to enable ES6 modules to be loaded dynamically on the client side, similar to what the browser would do. The library weights in at about 19kb gzipped. The used javascript modules have to be transpiled in it’s own systemjs module format. It is currently discussed to provide an integration layer to the new package manager yarn, which would make managing packages for SystemJS integration a lot easier.. (Read why we switched to yarn recently)

Webpack 2 provides us with asynchronous support for assets with the forward-facing “System.import” syntax, which allows us to very conveniently use it, for example, with react-router. But beware: Files included with System.import aren’t subject to tree-shaking, so every dependency will get included. As all imports have to be statically analyzable per ES6 module specification it is not possible to import assets based on runtime values.

Conclusion

Since all package formats and build tools look to solve the same problems of JavaScript interoperability, the improving support for the ES6 module syntax gets the javascript community on common ground. The advanced features of webpack aside, Rollup really looks like the way to go instead of solutions like browserify.

In the context of asynchronous loading and multiple entry points you should always try to estimate the possible bandwidth savings in advance. Even in large apps the significance of asynchronous module loading and/or code splitting can be minimal or even detrimental. Taking 5 minutes to optimize that png often saves you more bandwidth (and hot, hot tears of desperation) than hour long optimizations of your javascript bundle.

If you have any pointers or questions, I’m happy to hear from you on twitter.