Graduate Program KB

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

  1. Identify Entry point or points

    • Requires some form of directory traversal.
  2. Resolve Dependency Graph

    • Scan the entry point and recursively traverse all import and require statements.

    • Each module's dependencies are resolved.

    • Allows the bundler to figure out the exact files needed based on the import paths.

  3. 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);
    
  4. Concatenation

    • The bundler concatenates all the resolved modules into a single file.
    • This single file includes all necessary code to run the application.
  5. 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 and export.
  • 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 your package.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';


Return