In the early 2000s it was common practice to write just one main script for an entire website. Dependencies were in separate files that had to load before the main script, such as jQuery (released in early 2006) for handling cross-browser differences. It was normal to see inline scripts in
<head> or at the end of
</body>, as well. There were quite a few problems with this approach, such as polluting the global scope, but it worked for the low complexity of web applications at the time.
CommonJS Modules to the rescue
<script> tag. (Or there can be multiple bundle files, but more on that later.)
However, if the code is not human-readable it won’t be easy to debug in the browser. That’s the reason sourcemaps were created, first seen with Google’s Closure Compiler. Sourcemaps act like decoders and enable browsers to parse the minified code and put formatting back in. They also contain references to the lines in the original files, which means it’s possible to track bugs to the exact source. Bundler tools offer an option to create a sourcemap file at the time of minifying, which is co-located with your bundle.
How does bundling relate to code splitting?
Code splitting to create multiple scripts
The most common reason to have multiple scripts for a single application is to take advantage of browser caching, which stores the file for later use and doesn’t require another load request to the server. For example, a vendor bundle with dependencies that are unlikely to change, such as utility libraries for math or date calculations. That way even when the application changes a returning user won’t need to load the unchanged vendor bundle.
Tree shaking to help reduce bloat
When a module is included the whole package gets added to the bundle, even if only a part of it is used. It can be difficult for bundlers to know if code has side effects, which means that the code has dependencies on variables within scope but not within the explicit export. Without being able to distinguish what is being used and what is dead code, bundlers include the entirety of modules. This causes unnecessary additions to file size. Put another way, importing only one thing from a module with more than one export will mean the unused export is included in the bundle.
However, with some configuration, bundlers can eliminate unused exports and other dead code in a module. Eliminating dead code is often called “tree shaking”. By telling Webpack what parts of the code have no side effects it can shake the dead code out of the “tree”, or bundle. Using ESM is the most effective way to enable tree-shaking. If that isn’t an available option, which is still common today, then additional configuration is needed.
Dynamic require and lazy-loaded modules
Tree-shaking and code splitting a web application reduces the bundle file size significantly, but it doesn’t stop there. One of the main attractions of modern ECMAScript Modules is the ability to load modules as-needed. A rarely used part of an application can weigh down a bundle size unnecessarily for most users. Instead, it’s possible to dynamically load a module later instead of including it in the bundle file.
On-demand, “lazy-load” of a module is part of the specification for ES6. The syntax follows different rules, since it relies on Promises: the module is conditionally loaded when the import function is called. This means that the rarely used feature mentioned earlier is only referenced by the bundle and only loaded by the browser when a user accesses the feature, hence the on-demand nature of dynamic imports.
- Webpack – A Detailed Introduction
- Brief History of Modularity
- Exploring ES6, Chapter 16: Modules
- Evolution of the web
- A brief history of Node.js