top of page

Rollup vs. Parcel vs. webpack: Which Is the Best Bundler?

Recently I was publishing a library to npm and I thought of experimenting with the bundler I was going to package my code in. While webpack has always been my standard choice, I decided to put it up against two other popular bundlers — Rollup and Parcel.

For those coming from a non-JavaScript background, a bundler is a tool that recursively follows all imports from the entry point of your app and bundles them up into a single file. Bundlers can also minify your files by removing unnecessary white spaces, new lines, comments, and block delimiters without affecting their functionality.

Let’s try to understand this through a simple code snippet:

var test = [];
for (var i = 0; i < 100; i++) {
  test[i] = i;

We just created an array called test and initialised its members till 100. The minified version of this code will look somewhat like this:

for(var a=[i=0];++i<20;a[i]=i);

Fewer characters and lines. You might say the code is not readable, but who cares? You bundle your code once it’s ready, and minified code is easy to fetch and interpret for the browser.

It must be now easy for you to guess the importance of a bundler, right?

Suppose your code is not bundled and hosted as a package of multiple files on the server. For the import of each of these files to make your code run, the browser has to send a separate HTTP request to the server. The efficiency of this transmission is directly proportional to the number and size of the files being requested. In the case of large apps such as Facebook, this can lead to disastrous performance and UX. However, the performance of the app substantially improves with a bundler on board as now the browser only has to request a single file to display your app to the user. Moreover, fetching a minified file weighing a few KBs is faster than the actual file, which might run into MBs, resulting in improved load time of the app.

Why Install Bundlers When We Can Do It on Our Own?

Sure you can, but when working with huge codebases, minifying the app manually isn’t a scalable solution. Let the bundler do it for you!

Making the right choice of a bundler from the many available can be life-changing for your app, depending on the use case. Coming back to the experiment I was talking of in the beginning: I thought of sharing with you my findings on how webpack, Rollup, and Parcel fared on some important requirements a developer would have.

Configuration of the bundler

Parcel wins here as it doesn’t require a config file at all. Just install Parcel and run Parcel build, and it will do everything for you out of the box.

webpack and Rollup both require a config file specifying entry, output, loaders, plugins, transformations, etc. However, there’s a slight difference:

  • Rollup has node polyfills for import/export, but webpack doesn’t.

  • Rollup has support for relative paths in config, but webpack doesn’t — which is why you use path.resolve or path.join.

webpack config can get complex, but it provides extensive support for third-party imports, images, CSS preprocessors, and whatnot.

I had a hard time using Rollup for bundling my app that used axios, a very commonly used library for making HTTP requests — not just axios, but for other third-party integrations too. I had to research a lot and try installing many Rollup plugins before attaining victory — at the cost of dropping some imports.

Dead code elimination

Dead code elimination, or Tree shaking, as it’s often called, is very important to achieve the optimum bundle size and hence app performance.

Parcel emerged as the winner here. Parcel supports tree shaking for both ES6 and CommonJS modules. This is revolutionary since most of the code in libraries on npm still uses CommonJS.

Most of the work Parcel does when tree shaking is also done in parallel through multicore processing using numerous worker processes and is also cached on the file system. This means that builds are still fast and rebuilds are like blazingly fast.

Rollup comes second in the race. Right out of the box, it statically analyzes the code you are importing and will exclude anything that isn’t actually used. This saves you from writing more lines into your config, adding extra dependencies and bloating the size of your app.

webpack requires some manual effort to enable tree-shaking:

  • Use ES6 syntax (i.e. import and export).

  • Set the SideEffects flag in your package.json.

  • Include a minifier that supports dead code removal (eg: UglifyJSPlugin).

Rollup and webpack have focussed more on tree shaking for ES6 since it is much easier to statically analyze, but in order to truly make a big impact, we need to analyze CommonJS dependencies as well, for which they both require plugins to be imported.

However, given the fact that JavaScript is dynamic, almost every construct in the language can be altered at runtime in impossible to predict ways.

Practically, this means that an entity such as a class, which is imported as one reference, cannot be statically analyzed to remove members (both static and instance) that are not used. So rather than overly relying on the bundler to do it for you, it will be a good practice to visualize your components before you code and analyze them afterwards to get the best results.

Code splitting

As your app grows, your bundle size will grow too, more so with third-party imports. The load time of your app is directly proportional to its bundle size.

Code splitting helps the browser lazy-load just the things that are needed to get the app running, dramatically improving the performance and UX.

webpack emerges the winner in this aspect, with minimal work and faster load time. It provides three approaches to enable code splitting available in webpack:

  • Define entry points — Manually split code using entry configuration.

  • Use the CommonsChunkPlugin to de-dupe and split chunks.

  • Dynamic imports — use inline function calls within modules.

During code splitting by Rollup, your code-split chunks themselves are standard ES modules that use the browser’s built-in module loader without any additional overhead, while still getting the full benefit of Rollup’s tree-shaking feature. For browsers that don’t yet support ES modules, you can also use SystemJS or any AMD loader. It is completely automated and results in zero code duplication.

Parcel supports zero-configuration code splitting. Here code splitting is controlled by use of the dynamic import() function syntax proposal, which works like a normal import statement or require function, but returns a Promise. This means that the module is loaded asynchronously.

It was tempting to favour Rollup and Parcel over webpack for code splitting, but both of them have only recently introduced this feature and some issues have also been reported. So it’s safe to stick with the good old webpack.

One compelling fact I noticed was that for the same code with code splitting enabled, the build time was least with webpack, followed by Rollup, and lastly Parcel.

Live reload

During development, it’s great if your app gets updated with fresh code that you write, instead of manually refreshing it to see the changes. A bundler with live reload capability does that refreshing for you.

Bundlers provide you with a run-time environment in addition to other utilities essential for debugging and development, in the form of a development server.

Parcel has been very thoughtful by having a development server built in, which will automatically rebuild your app as you change files. But there are issues associated with it when using HTTP logging, Hooks, and middleware.

When using Rollup, we need to install and configurerollup-plugin-serve, which will provide us with live reload functionality. However, it needs another plugin, rollup-plugin-livereload, to work. That means it’s not an independent plugin and comes with an extra dependency to run.

With webpack, you just need to add one plugin, called webpack-dev-server, which provides a simple development server with live reload functionality turned on by default. What’s better? You can use Hooks to do something after the dev server is up and running, add middleware, and also specify the file to serve when we run the dev server. This customisability of webpack trumps Rollup and Parcel.

Hot module replacement

Hot module replacement (HMR) improves the development experience by automatically updating modules in the browser at run time without needing a whole page refresh. You can retain the application state as you make small changes in your code.

You might ask how HMR is different from live reload.

Well, live reloading reloads the entire app when a file changes. For example, if you were five levels deep into your app navigation and saved a change, live reloading would restart the app altogether and load it back to the landing/initial route.

Hot reloading, on the other hand, only refreshes the files that were changed while still maintaining the state of the app. For example, if you were five levels deep into your app navigation and saved a CSS change, the state would not change: You’d still be on the same page, but the new styles would be visible. webpack has its own web server, called the webpack-dev-server, through which it supports HMR. It can be used in development as a live reload replacement.

While Parcel already had built-in support for hot module replacement, Rollup released a plugin rollup-plugin-hotreload last month to support hot reload.

As this capability is fairly new in bundlers like Rollup and Parcel, I still choose webpack as the safe bet for I don’t want to run into avoidable issues during development.

Module transformers

Bundlers generally know only how to read JS files. Transformers are essentially teachers who teach the bundler how to process files other than JS and add them to your app’s dependency graph and bundle.

Loader for transforming css modules

For example, in the image above, you can see a webpack config having an instruction on how to read CSS between lines 13 to 15. It basically says, “Hey webpack, whenever you encounter a file that is resolved as .css, use css-loader imported above to read it and export it as a string.” Similarly, an HTML loader will tell webpack how to read the .html files it encounters in your app and export them as strings in your bundle.

Parcel handles the transformation process very smartly. Unlike Rollup and webpack, which need you to specify file types to transform, install and configure, plugins to transform them, Parcel provides built-in support for many common transforms and transpilers.

Parcel automatically runs the appropriate transformer when it finds a configuration file such as.babelrc, .postcssrc, .posthtml, etc. in a module. In addition to any transforms specified in .babelrc, Parcel always uses Babel on all modules to compile modern JavaScript into a form supported by browsers.

In a Nutshell

Following is the crux of the findings from my experiment:

Building a basic app and want to get it up and running quickly? Use Parcel.

Building a library with minimal third-party imports? Use Rollup.

Building a complex app with lots of third-party integrations? Need good code splitting, use of static assets, and CommonJs dependencies? Use webpack.

Source: Medium

The Tech Platform


Recent Posts

See All


bottom of page