• Technicalpig
  • Posts
  • TechnicalPig🐷: Understand module bundlers

TechnicalPig🐷: Understand module bundlers

Webpack, Esbuild, What are they?

Bundlers

Bundlers like webpack and esbuild bundle modules together into static assets that are optimised for the web browser.

Most bundlers are static module bundler - this means it bundles as a build step and not dynamically at runtime.

What is a Module? A module is simply a file. In your file e.g. doSomething.js you will likely have a chunk of code or asset that performs a function. Modern JavaScript supports modules natively (using import and export statements), allowing developers to write maintainable and modular code.

Why bundle modules? it is primarily about efficiency. Bundling modules is a process where multiple JS files are compiled and merged into a single file (or a few files) improving performance like load time. Here are the benefits:

  1. Network Efficiency

    • Fewer requests: Each separate file the browser needs to load is a separate HTTP request. More files means more requests which can slow down page loading. Bundling into less files means less HTTP requests.

    • Caching: A single bundle file can be cached by the browser which means subsequent page loads will be faster because the browser already has the code stored in the local’s browser cache.

  2. Performance Optimisation

    • Bundling often includes other optimisation like minification (removing unnecessary code like whitespaces) and tree shaking (remove unused code), making the modules smaller.

    • Code Splitting: modern webpack allows you to split your code into multiple bundles which can be loaded on demand or in parallel, optimising load time.

  3. Consistency and Compatibility

    • Bundlers can use tools like Babel to transpile newer JS syntax into a version compatible with old browers, allowing your code to work across wider environments.

    • Module resolution: While Node.js natively supports CommonJS, web browsers support ES Modules. Bundlers help bridge this gap by resolving modules in a way that they can be correctly imported and used in the browser.

  4. Development Experience

    • Sourcemaps: Bundlers generate sourcemaps which map the combined and minified code back to the original source files. This helps with debugging - allows you to see where in the original code the error happened.

    • Hot Module Replacement: changes to the code can be instantly reflected in the browser without needing a full page reload, speeding up development

How bundling works

As explained above, bundling is about efficiency - optimising the loading and execution of web application in a browser and improving the development process.

We will delve into how bundling combines and optimises multiple modules into a smaller number of modules.

  1. Analysis: the bundler starts with an entry point. It then identifies dependencies by looking at import statements.

  2. Dependency Graph Construction: using this information, it constructs a dependency graph. The graph represents all the modules and how they are interconnected. It arranges the modules in the correct order (a module must be defined because it is used)

  3. Bundling: Using the order defined in the graph, the bundler then merges the code into 1 file (or multiple). It wraps the module code in functions to maintain scope isolation (prevents variables and functions from different modules from clashing in the global scope).

  4. Optimisation: the bundler then executes things like minification, tree shaking, transpiling and polyfills (provide modern functionality on older browsers).

  5. Output: The final step is generating the output files. This includes the bundled file and often includes the sourcemaps.

How does static bundling work with dynamic imports

Modern JS module syntax like ECMAScript allows you to dynamically import modules and runtime.

Dynamic imports use the import() function to load a module asynchronously. When you use import() with a module path, it returns a promise that resolves to the module. This lets you conditionally and lazily load modules, components, libraries, etc., at runtime, depending on user actions or other conditions.

Even though static module bundlers run at build time, they are designed to recognise and specifically handle dynamic imports:

  1. Code Splitting: When a bundler encounters a dynamic import, it automatically splits the code at that point, creating a separate bundle (often called a "chunk") for the dynamically imported module. This chunk is loaded only when the import() statement is executed at runtime.

  2. Loading Mechanism: The bundler generates additional JavaScript code to handle the loading of these chunks at runtime. This includes fetching the chunk, caching it if necessary, and executing it once it's loaded. This process is seamless from a developer's perspective—your code simply uses import(), and the bundler takes care of the rest.