JS Bundling 101
What is Bundling?
Bundling is the process of combining multiple files and assets into a single file or far fewer files. This process is typically done using a bundler like Webpack.
Purpose of Bundling
- Optimising Delivery.
- Dependency Management.
- Less Strain on the Server.
- Developer Experience.
How it Works - High Level
-
Identify Entry point or points
- Requires some form of directory traversal.
-
Resolve Dependency Graph
-
Scan the entry point and recursively traverse all
import
andrequire
statements. -
Each module's dependencies are resolved.
-
Allows the bundler to figure out the exact files needed based on the import paths.
-
-
Module Wrapping
- Each module is wrapped in a function to ensure that its scope is isolated from other modules.
- Prevents variable name collisions and maintains modularity.
- Modularity helps ensure that each module can be developed and tested independently with clear boundaries.
- Enforces a clear separation of concerns between different parts of the code.
// Original module code var myVar = 'Hello, World!'; function myFunc() { console.log(myVar); } // Wrapped module code (function(module, exports) { var myVar = 'Hello, World!'; function myFunc() { console.log(myVar); } module.exports = myFunc; })(module, module.exports);
-
Concatenation
- The bundler concatenates all the resolved modules into a single file.
- This single file includes all necessary code to run the application.
-
Optimizations
- Minification: Removes whitespace, comments, and other non-essential characters.
- Tree Shaking: Unused code is eliminated from the bundle.
- Code Splitting: The bundler can split the code into chunks that are loaded on demand, improving initial load times and optimise resource usage.
- Hot Module Replacement: Allows developers to update modules in a application without requiring a full page reload.
Brief Historical Context
- Number of HTTP requests could get really out of hand as projects grew.
- Slowing load times.
- Affecting the user experience.
- Bundling became prominent to accommodate the complexities of building large-scale applications.
- The rise of AJAX and single-page applications enticed a need for faster load times.
- JS bundling emerged to solve this problem, reducing many files into one.
Module Systems
Common JS
- Module system for JavaScript designed for server-side JS environments (node).
- Uses the
require
keyword to get items from other files. - Each file is treated as a module by default.
- Has limitations in the browser, particularly to do with asynchronous loading.
- Pros
- Simplicity.
- Node.js Compatibility.
- Cons
- Synchronous Loading, not suitable for environments where async is needed.
- Lack of standardization, not natively supported by browsers.
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
module.exports = {
add,
subtract
};
// main.js
const { add, subtract } = require('./math.js');
console.log(add(5, 3)); // Outputs: 8
console.log(subtract(5, 3)); // Outputs: 2
Asynchronous Module Definitions
- Another module format that was designed for addressing the challenges of loading JS modules asynchronously in web browsers.
- Gained popularity in the transition period whilst ES6 wasn't yet available in browsers.
- One of its key features is the ability to define and load modules asynchronously.
- Pros
- Asynchronous Loading.
- Browser Focused.
- Cons
- Verbosity, syntax can be harder to read.
- Overhead, additional overhead of managing async dependencies.
// math.js
define([], function() {
return {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
};
});
// main.js
require(['math'], function(math) {
console.log(math.add(5, 3)); // Outputs: 8
console.log(math.subtract(5, 3)); // Outputs: 2
});
ES Modules
- Another way of organizing and structuring JS code into separate files, allowing for better modularity and code organisation.
- Uses
import
andexport
. - Each ES Modules is its own file, every file acts as a separate module.
- Default exports and named exports.
- Can handle circular references between modules because the imports are live bindings to exports.
- Support static analysis, meaning that imports and exports are resolved during parsing.
- Which allows tools like bundlers to optimize code.
- Supports asynchronous loading, making them suitable for web applications where tree-shaking and code splitting are common patterns.
- Supported in modern browsers using the
<script type="module>
tag. - Supports Node.js, you can either make a
.mjs
file or add"type": "module"
in yourpackage.json
file. - Pros
- Standardized.
- Static Analysis, enabling better optimizations.
- Asynchronous Loading.
- Cons
- Compatibility, requires transpilation (babel) for older environments.
// math.js
export function add(a, b) {
return a + b;
}
export const pi = 3.14159;
// welcome.js
default export greeting() {
return "Hello";
}
// main.js
import { add, pi } from './math.js';
import MyGreeting from './welcome.js';