How JavaScript works: a guide to build tools + exploring Webpack, Parcel, Rollup, ES Build, and Snowpack

Lawrence Eagles
SessionStack Blog
Published in
11 min readDec 3, 2021

--

This is post # 54 of the series, dedicated to exploring JavaScript and its building components. In the process of identifying and describing the core elements, we also share some rules of thumb we use when building SessionStack, a JavaScript tool for developers to identify, visualize, and reproduce web app bugs through pixel-perfect session replay.

Introduction

In its simplest form, JavaScript build tools are programs that turn development code into production code.

Over the years, build tools in JavaScript have evolved from task runners such as Grunt and Gulp to bundlers such as Browsify and Webpack, and recently we have modern module bundlers such as rollup, ES build, and Snowpack.

The primary use of build tools is to make a developer’s life easier. That is to provide an awesome developer experience.

Build tools help developers handle annoying repetitive tasks, and transpile our code to format the browser can understand. Thereby, enabling developers to leverage useful libraries and frameworks that the browser cannot understand by default. Tools like React, Vue, TypeScript, Loadash, and more are very useful in web development, but the browser does not understand the code written with these libraries. However, with a build, we can develop our app using any tool of choice and transpile our code into a final production-ready bundle that the browser can understand.

This is a list of some of the features of a JavaScript build tool:

  1. Use modern libraries or frameworks of choice
  2. Support multiple modules definition
  3. Minification
  4. Concatenation
  5. Code splitting
  6. Tree shaking
  7. Hot Reload
  8. SASS or CSS frameworks
  9. Browser compatibility — removes different configurations and loading polyfills

In this article, we will focus on modern module bundlers in JavaScript.

Let’s start learning about them in the next section.

Modern JavaScript Build Tools

These modern build tools are also called bundlers or module bundlers. The primary function of a build tool is to combine multiple JavaScript files into one single file or bundle that can be used in the browser to load your application. This file or JavaScript bundle contains all our code and the third-party dependencies that are imported and used in the code.

Note, in some cases, your dependencies can have a dependency, and bundlers such as Webpack keep a dependency graph to track how everything is put together.

Bundlers are dev dependencies that help build our application code and assets for production. For example, images are optimized, dead code is removed by tree shaking, and JavaScript can be minified or split into chunks and lazy-loaded to improve performance.

In the next section, we will start learning about different module bundlers in JavaScript.

Webpack

Webpack is the swiss army knife of JavaScript module bundlers and it is an advanced tool that ships out of the box with an overwhelming amount of features. And this can make Webpack difficult to learn.

As Webpack analyzes our application it builds a dependency graph that keeps track of all our application dependencies. The dependency graph includes all the modules our application depends on and their dependencies and non-core assets, such as images or web fonts.

To work with Webpack you need to understand its core concepts:

  • Entry

This refers to our application entry point. It tells Webpack which file to begin building out its internal dependency graph.

By default, this is set to src/index.js. If the path in your application is different it won’t work.

  • Output

Once Webpack correctly points to our app’s entry file, it will analyze the code in the entry file, look up all our app dependencies, and import and compile our code into a main.js file in a dist folder. This main.js file is our production code that gets shipped to the end-user and the browser.

  • Loaders

Out of the box, Webpack only supports JavaScript and JSON files but by using loaders, Webpack handles preprocessing non-JavaScript files such as TypeScript, CSS, HTML, Markdown, etc.

  • Plugins

Also, Webpack allows us to hook into the compilation or bundling lifecycle by using plugins and perform tasks such as tasks like bundle optimization and asset management.

  • Mode

The mode parameter can either be none, development, or production. And depending on the configuration we enable Webpack’s optimization for that environment.

  • Browser Compatibility

Webpack supports all browsers that are ES5-compliant and as a result, IE8 and below are not supported.

To use Webpack we install it as seen below:

https://gist.github.com/lawrenceagles/c71bceaf66d0835e90aac4f3f31f29fb

Then we update NPM scripts to run the webpack command as seen below:

https://gist.github.com/lawrenceagles/9e8fc3e30b8a6658c27293ea8442c7cd

You can configure Webpack by creating a webpack.config.js file. This file is a JavaScript module that exports an object that customizes the behavior of Webpack:

https://gist.github.com/lawrenceagles/84df6c89ab01165d1a2dbc6badb8cb06

In our simple Webpack configuration object above, the entry points to our entry file, and the output.filename points to the file name of our production file or final bundle. We used Node.js path module to get a more consistent path in our configuration.

There can be more complex configurations for example to handle code-splitting, etc. but the above-contrived example is simply for the purpose of the tutorial.

Pros

  • Feature-packed
  • Great community and backing
  • Good CSS support
  • Great community plugins
  • Loader for anything you need
  • Code-splitting
  • Tree-shaking
  • Supports multiple module definitions

Cons

  • Not beginner-friendly
  • Difficult to master

Parcel

Parcel is a plug-and-play module bundler that requires zero configuration. It is a beginner-friendly bundler that focuses on simplicity and works out of the box.

Parcel offers a great developer experience with a scalable architecture for building production-ready applications. And It eliminates the pain of fiddling with config files and keeping up with best practices.

The Parcel uses a multi-architecture design that enables it to build our application code in parallel by using worker thread and utilizing all our machine cores in the process. This makes the Parcel build process very fast. In addition, Parcel uses a Rust compiler for transpiling JavaScript, JSX, and TypeScript and it automatically includes content hashes in the names of all output files. And this results in long-term caching.

Also, Parcel makes debugging easy by displaying beautiful diagnostics in the terminal.

As our application grows and build requirements become complex, we can easily extend Parcel by adding our configurations in a .parcelrc file.

To use Parcel we, install it as a dev dependency as seen below:

https://gist.github.com/lawrenceagles/2b4190112db7b3e1cc71f3ac6fa819ad

Then we update our NPM scripts as seen below:

https://gist.github.com/lawrenceagles/16958e42e1c258680f854507d60d3bbb

Now we can run the parcel command and start our dev server by running npm start, this would serve our application at http://localhost:1234/.

Parcel accepts any type of file as the entry point, and it would follow up all the dependencies from there to build our app.

Also, we can run npm build to build our app for production.

Pros

  • Great for beginners
  • Hot reloading
  • Content hashing
  • Minification
  • Code splitting
  • Tree shaking
  • Friendly Error logs
  • Awesome developer experience

Cons

  • Webpack is preferred for large projects.
  • The documentation is not robust

Rollup

This is the default module bundler for Svelte. Rollup is a blazingly fast, modern module bundler that uses the standard ECMAscript module definition— ES module.

Rollup can be used via its JavaScript API or the CLI which is the easiest way to use Rollup.

Like Webpack, Rollup supports plugins that can be used to change the behavior of Rollup during the build process.

Plugins add flexibility to the build process by handling tasks such as importing NPM modules, compiling codes with Babel, working with JSON, etc.

To use Rollup we install it as seen below:

https://gist.github.com/lawrenceagles/6a8e10acc8263c39b91b3591f5b90fe7

Now we can try it out by running the rollup command which would print help instructions because there is no configuration or no arguments were passed. This is the same as running rollup -h or rollup — help.

We can specify the entry file and an output file as CLI arguments as seen below:

https://gist.github.com/lawrenceagles/c47ef13d65fe48b3277bf67695c2155a

In the code above, the first argument src/main.js is the entry file, the second argument bundle.js is the output file, and the last argument cjs is the module format in this case CommonJS.

Repeating these arguments in the CLI can be annoying so to save ourselves the headache we can use the Rollup config file.

A Rollup config file is a file named rollup.config.js that is created in our root directory. It gives us more flexibility and a better developer experience than using CLI arguments.

Consider the code below:

https://gist.github.com/lawrenceagles/a8a63746227d116643a2d2526821dc24

With our entry file, output file, and module format specified in our config file as seen above, we can now run the rollup command by using the — config or -c flag: rollup — config or rollup -c.

We can leverage the flexibility of the config file by creating more than one config file that is run independently depending on our environment.

For example, we could have a config file named rollup.config.dev.js and another named rollup.config.prod.js. And these can be run selective depending on the environment as seen below:

https://gist.github.com/lawrenceagles/bf483fb2d89e7dde2b3249786d0bf5fe

If Rollup is used locally, we run the rollup command by using NPM scripts, and for this, we will modify our package.json scripts as seen below:

https://gist.github.com/lawrenceagles/616196f3136058ffdb3517960f513dd8

Vite is an opinionated superset of Rollup. In a nutshell, Vite is a wrapper around Rollup that aims to improve performance by leveraging JavaScript tools written in compile-to-native languages. And for this, Vite uses ES build under that hood.

Pros

  • Simple API design
  • Easy to write plugins
  • Well documented
  • Tree shaking
  • Code splitting
  • Fast builds

Cons

  • Does not support async/await out of the box.

ES Build

ES build is an incredibly fast JavaScript bundler built with Go.

ES build can be used via the CLI but it also provides APIs for JavaScript and GO.

ES build takes an array as its entry file and each file in this array serves as an input to the ES build bundling algorithm.

While a simple app may need only one entry file, multiple entry points are useful if there are multiple logically-independent groups of code such as a main thread and a worker thread.

Also, we can separate concerns and reduce the amount of unnecessary code the browser downloads by using multiple entry points.

To specify entry points we pass an array of file paths to ES build as seen below:

https://gist.github.com/lawrenceagles/6806c1a64a91687b961e10f8ffe3d385

In the code above, both home.ts and dashboard.ts are entry points that would generate the corresponding output files: /out/home.js and /out/dashboard.js.

If a single entry point is specified then we use the outfile option instead of the outdir option used with multiple entry points above.

Consider the code below:

https://gist.github.com/lawrenceagles/3951d05bd0d403776ab077951274a200

Also, like Webpack, ES build support loaders and plugins. Loader change how ES build interprets files. For example, the js loader interprets files as JavaScript and the css loader interprets files as CSS. And plugins allow us to inject code into the ES build, build process.

Pros

  • Blazing fast
  • Source map generation
  • Tree shaking
  • Minification
  • CLI, JavaScrit, and GO APIs
  • Transpilation of JSX to JS
  • Loader for TypeScript, JavaScript, and JSON
  • Support different module formats: CJS IFFI, and ESM

Cons

  • The plugin API is still experimental so it is unstable.
  • Code splitting is still a work in progress it currently only works with ESM.

Snowpack

Snowpack is a modern JavaScript module bundler unique for its unbundled development idea. While other bundlers use the bundled development pattern that involves running our entire application through a bundler, Snowpack implements a novel pattern that involves shipping individual files to the browser during development.

The unbundled development pattern involves serving your application unbundled during development. Each file is built once and cached, and when a file changes, Snowpack rebuilds only that file. And the result of this is that we get much faster build time since we are not rebuilding our enter application with each file change.

To use Snowpack we can install and run the Snowpack CLI command as seen below:

https://gist.github.com/lawrenceagles/c7ee9f4e9c2a6dce3e2b045dc102a77a

Next, we update package.json scripts as seen below:

https://gist.github.com/lawrenceagles/5657ebc21a11c6074ec6e8beffd7558b

Now we can use Snowpack by running the code below:

https://gist.github.com/lawrenceagles/8c9f51ca6d2654b3d629edfa6d4a36ac

Pros

  • Unbundled development pattern
  • Fast refresh and HMR
  • Great Cache implementation
  • Simple API
  • Plugins support

Cons

Conclusion

JavaScript build tools make developer’s life easier. While Gulp and Grunt are great task runners, the current trend is to use a modern module bundler and NPM script as the task runner.

Webpack is great feature-packed and heavy. Parcel is great for beginners and works out of the box.

Vite is an opinionated super of Rollup and uses ES build under the hood to enhance performance.

Lastly, Snowpack, with its unbundled development is the feature.

Build tools makes it much easier for products like SessionStack to have efficient and high-performing code. SessionStack collects behavioral data during customer journeys, allowing you to replay them as a video. Since SessionStack efficiently collects and processes behavioral data, build tools are an important aspect of our stack that allows us to deliver a reliable and robust service.

There is a free trial if you’d like to give SessionStack a try.

SessionStack replaying a session

If you missed the previous chapters of the series, you can find them here:

--

--