Once upon a time in web development, building web pages required just HTML, CSS, and some JavaScript for interactivity. This was the typical code structure of most websites:
Most often, the developers will introduce third-party JS libraries for extra interactivity.
JavaScript was perceived as a client-side scripting language and nothing more during this period. That would soon change, especially with the introduction of Nodejs. Node proved that JavaScript had more client-side capabilities, such as handling network requests, routing, complex animations, and storage.
Then we started building large-scale products like eCommerce sites, social media apps, learning platforms, e.t.c, with JavaScript. This introduced more third-party libraries, and CDN (content delivery networks) weren’t popular then, so you had to download the library JS files. This made code so messy and difficult to maintain, and the developer experience was horrible.
During this period, the ever-progressive JS community started focusing on improving developer experience with developer tools. This led to the birth of Bundlers.
Bundlers
A bundler is simply a development tool aggregating all JS files as input and outputs a single JS file that is loadable on a web browser. A bundler ensures that all source code and third-party dependencies are up-to-date and error-free. Before the era of bundling code, optimization and logging were major issues, a bundler solves that with features such as:
- Code splitting
- Hot module replacement (HMR)
- Loggers
So how do Bundlers work behind the scenes?
- Dependency Resolution: The bundler generates a dependency graph of all served files.
- Bundling: The bundler outputs static assets that the browser can parse. This process is referred to as Packing.
Bundlers like browserify have existed since 2010 using require
to load npm packages in the browser. However, JavaScript didn’t have a built-in module system until 2015, with the introduction of ES6. This new wave of modular programming led to the birth of module bundlers like webpack, rollup, parcel, and esbuild. Of all the bundlers, Webpack got everyone’s attention and is currently the most used bundler with ~26million weekly downloads.
Webpack
Webpack is a static module bundler. When introduced to a project, it generates a dependency graph from one entry point (more like index.js
) or more, and it combines all the modules (JS and non-JS) into one or more (depending on your configuration) bundles. These bundles become static files (html, css, js, assets) that the browser can process. It requires no configuration to bundle your project but is very configurable.
Let’s briefly take a look at how webpack approaches dependency resolution.
Entry Point: This is where webpack starts when building its internal dependency graph. By default, it is
index.js
, but you can choose a different entry point or more than one point.webpack.config.js
module.exports = { entry: ['../../index.js', '../../server.js'], };
Output: This is the location of the bundles. By default, the output property creates a
dist
folder.webpack.config.js
const path = require('path'); module.exports = { output: { path: path.resolve(__dirname, 'dist'), }, };
**Loaders**: Ever wondered how webpack parses assets such as HTML, CSS and media files? it uses Loaders. Loaders convert these files into consumable modules and add them to the dependency graph.
webpack.config.js
module.exports = { module: { rules: [ { test: /\.(js|jsx)$/, exclude: "/node-modules/", use: "babel-loader" }, { test: /\.html$/, use: "html-loader" }, { test: /\.(scss|sass)$/, use: ["style-loader", "css-loader", "sass-loader"] }, { test: /\.(png|jpe?g|gif)$/i, use: [ { loader: 'file-loader', }, ], }, ] } }
Plugins: One of the major reasons webpack is loved by all is its plugin system. Webpack plugins allow you to perform tasks such as bundle optimization, asset management, and injection of environment variables.
webpack.config.js
const BrotliPlugin = require('brotli-webpack-plugin'); module.exports = { plugins: [ new BrotliPlugin({ asset: '[path].br[query]', test: /\.(js|css|html|svg)$/, }) ] }
Mode: By setting mode, you choose the environment webpack optimizes for.
module.exports = { mode: 'development', };
These are the core concepts of webpack. If you’ve worked with JavaScript frameworks such as Vue, React, Angular, and so on, you’ll notice these core webpack concepts are implemented. This is because these frameworks use Webpack for bundling.
The impact of webpack in frontend tooling and architecture is vast. It's widely used in Single Page Applications (SPA), Server-Side Rendering Applications, and Static Site Generators (SSGs). In short, other language frameworks, such as PHP (Laravel) and Ruby(Rails), use webpack to manage JavaScript, CSS, and static assets like images or fonts.
Moreover, with the availability of native ES modules in the browser and the rise of JavaScript tools written in compile-to-native languages, the choice of bundler requires more attention. Webpack has issues such as:
Slow development server: The Webpack plugin system is one of its biggest pros and cons. The heavy reliance on plugins to perform certain tasks can slow down the bundler and hence increase the time it takes to start the development server.
Also, the entire application is rebuilt every time you make changes to a file. Imagine what happens in larger projects.
Complexity: As a project grows and more plugins are introduced, the configuration becomes more complex.
- It generates code that is impossible to read. This is always a problem, especially when refactoring code in a large project.
These issues led the ever-progressive JS community to develop better alternatives to Webpack. One of the most successful alternatives so far is Vite. Let’s briefly explore Vite.
Vite
Vite is a build tool that provides a faster development experience in web projects. Unlike bundlers, Vite consists of two parts:
- Rollup for code bundling
- A dev server with extensive features, including fast Hot Module Replacement (HMR)
The HMR API in Vite is considerably faster than Webpack. Vite solves the slow development server problem we had with Webpack, even as the project expands. Vite ushers in a new era of ESM-based development tools and Bundleless architecture. We won’t go deep into Vite cause it’s past the scope of this article, but the goal of introducing it is so you’ll note these points;
- Vite is a build tool, not a bundler. It’s faster, and most teams are migrating from webpack to Vite.
- Vite is not a direct replacement for Webpack. It’s just better than webpack in most cases.
These points raise the question, “What’s the successor to Webpack?”. A few weeks ago, Vercel answered that question with the release of Turbopack, and in this article, we’ll learn about what Turbopack is, its pros and cons, how to use it on a Nextjs project, what makes it better than Webpack, and we’ll briefly discuss the future of webpack-based projects.
Turbopack
Turbopack is an incremental bundler optimized for your JavaScript and Typescript projects. Unlike other bundlers written in JS/TS, Turbopack is Rust based. It’s the official successor to Webpack, and the creators of Webpack and Nextjs are building it.
Turbopack claims to be 700x faster than Webpack and 10x faster than Vite (Vite creator disagrees with this) in large projects.
So what makes Turbopack so fast?
- Compilation by Request: We’ve discussed the importance of startup time in developer experience and how the Webpack dev server takes seconds/minutes to start as the project gets bigger because it rebuilds the entire application every time a file is changed. Turbopack, on the other hand, compiles only the code needed to start the project.
- Turbo Engine: Turbo Engine is a powerful Rust library that enables incremental computation. In computer science, the incremental computation approach is when a sequence of inputs is slightly different from each other, it uses the previously computed output in computing a new output rather than computing the new output from scratch. This is applied in optimizing compilers, and one way to achieve incremental computation is through caching. Turbo Engine implements incremental computation using Function-level caching.
Let’s explore the Features and Cons of Turbopack.
Features and Cons of TurboPack
Let’s highlight some of the features of Turbopack
- Faster development server time: Turbopack supports Hot Module Replacement out of the box, and its HMR is way faster due to incremental computation. HMR ensures your dev server doesn’t fully refresh after every file change.
- Out-of-the-box support for JS & TS: Turbopack bundles JavaScript and Typescript but not with Babel. Turbopack uses a Rust-based compilation tool, SWC(Speedy Web Compiler). For context, SWC claims to be 17x faster than Babel.
- Out-of-the-box support for CJS and ESM imports: Whatever method you use to import modules, dynamic imports, ES Modules, or CommonJS, it’s supported by Turbopack.
- Live reloading for Environmental variables: One of the most annoying experiences when developing is having to close and reload your server after changing environmental variables. Turbopack ships with live reloading for environmental variables.
Let’s highlight some cons of Turbopack so far. It’s important to note that Turbopack is still very new and experimental, so these issues might be fixed as it matures.
- Lacks god-level Extensibility: Webpack had what I refer to as god-level extensibility with its plugin API. Turbopack doesn’t support plugins, but they promise that the bundler will be extensible in future versions. However, they won’t be porting the Webpack plugin API, meaning most Webpack plugins you enjoy today won’t work with Turbopack.
- Does not perform Type Checks: Turbopack uses SWC for compiling TS and doesn’t have out-of-the-box support for type checking. Like Vite and esbuild, with Turbopack, you must run
tsc --watch
or depend on your code IDE for type checking. - Only supports the Nextjs dev server.
How to use Turbopack
Turbopack is only at its Alpha version and has only been deployed as a Nextjs v13 dev server. To run a Nextjs v13 project powered by Turbopack, run this command on your terminal.
npx create-next-app --example with-turbopack
This command will bootstrap a Nextjs v13 sample with React Server components. Run yarn install
to install dependencies.
Now for the moment of truth. Let’s run the project with yarn dev
6.24ms! with 20+ components!
For context, let’s compare this startup time with a non-turbopack Nextjs v13 project with fewer components and dependencies.
Compiled client and server in 11s!
The difference is clear. We can only look forward to when Turbopack will be a low-level engine for other frameworks.
Migrating from Webpack
As we’ve discussed, Turbopack is still in experiment mode and is not yet ready for production environments. So at the moment, you can’t port your project to Turbopack.
What this means for Webpack users
Firstly, to webpack aficionados, Turbopack is a preview of the future. Webpack has ~26m downloads weekly, which will continue for as long as possible until the project maintainers pull off the plug.
If you visit Snowpack (not maintained anymore), you will be advised to use Vite. The same thing will happen with Webpack and Turbopack in the future.
However, Turbopack is managed by Vercel, and we don’t know when the bundler will be ready for wide adoption. I recommend Vite if you’re looking for an alternative to Webpack.
Conclusion
Turbopack is a promising project that will redefine bundling tools architecture in an era where build tools such as Vite and esbuild are replacing bundlers. In this article, we learned what Bundlers are and how they work. Then we introduced Webpack as the bundler par excellence tool; we went on to learn its core concepts and briefly explored Vite as an alternative to webpack. Furthermore, we discovered Vite is a build tool and not the successor to Webpack. This introduced us to Turbopack. We learned what Turbopack was, how it worked, its top features and issues, how to use it in a project, and how it affects existing Webpack users.