Skip to main content

Command Palette

Search for a command to run...

Evolution of JavaScript Modules

Updated
5 min read
Evolution of JavaScript Modules
A

Software Engineer | Open Source Enthusiast | Mentor | Learner I love documenting stuff that I come across and find interesting. Hoping that you will love reading it and get to know something new :)

JavaScript development involves many concepts and tools that help developers organize, share, and reuse code effectively. For newcomers, terms such as library, framework, UMD, and CDN can seem technical, and understanding their roles is essential for building modern web applications. This article explains these concepts, their significance, and how they interrelate.


Libraries

A library is a collection of pre-written code that provides specific functionality to developers. Instead of writing functions from scratch, developers can use libraries to perform common tasks efficiently. Libraries focus on solving particular problems and allow developers to selectively use the functions they need without imposing an application structure.

For example, Lodash is a widely used JavaScript library that provides utility functions for arrays, objects, and strings. Its methods can simplify tasks like merging arrays or deep-cloning objects. A library can be included directly in an HTML page through a <script> tag from a CDN, or installed in a project using package managers like npm or yarn.

Example of using Lodash via a CDN:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script>
  console.log(_.chunk([1, 2, 3, 4], 2)); 
  // Output: [[1, 2], [3, 4]]
</script>

Libraries are flexible and reusable, and they empower developers to focus on application logic rather than implementing utility functions repeatedly.


Frameworks

A framework differs from a library in that it provides a structured approach to building applications. Frameworks often dictate the architecture, workflow, and code organization, while libraries offer standalone functionality. When using a framework, developers write code that integrates into the framework’s prescribed structure.

Angular is a well-known JavaScript framework for building large-scale web applications. It includes tools for routing, state management, and component-based UI rendering. Developers follow Angular’s conventions and lifecycle hooks to ensure their code works seamlessly within the framework.

React, although often referred to as a library, behaves like a framework when combined with supporting tools for routing, state management, and build systems. Frameworks provide both functionality and structure, whereas libraries focus solely on specific capabilities.


UMD (Universal Module Definition)

JavaScript has had multiple module systems over the years, including CommonJS for Node.js, AMD for asynchronous browser loading, and the modern ES Modules standard. These differences created compatibility challenges for developers distributing code for multiple environments.

UMD, or Universal Module Definition, was developed to address this issue. A UMD build ensures that a library can work across all major module systems:

  • As a global variable in a browser when included with a <script> tag.

  • As a CommonJS module in Node.js using require().

  • As an AMD module using define() with loaders like RequireJS.

A simplified UMD wrapper looks like this:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory); // AMD
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory(); // CommonJS
  } else {
    root.MyLibrary = factory(); // Browser global
  }
}(this, function () {
  return {
    greet: function(name) {
      return "Hello " + name;
    }
  };
}));

Including this script in a browser via a <script> tag makes the MyLibrary object available globally:

<script src="my-library.umd.js"></script>
<script>
  console.log(MyLibrary.greet("World")); // Output: Hello World
</script>

UMD ensures that a library is universally usable, reducing the need for environment-specific builds.


Why UMD Matters?

UMD is important because it guarantees broad compatibility. Developers can package their library once and use it in Node.js, modern ES module environments, or directly in the browser. This simplifies distribution and adoption, particularly for libraries intended for multiple platforms.

Even with the adoption of ES Modules, many popular libraries, such as Lodash and Moment.js, continue to provide UMD builds. These builds are especially useful for quick prototyping or embedding libraries in static web pages without a build system.

  • If you publish a library (e.g., React, Lodash), you often provide a UMD build so developers can:

    • Use it with <script> tags directly.

    • Or import it with require() or define().

  • Modern projects often ship multiple builds:

    • ESM (.mjs) → for modern bundlers (tree-shaking, import/export).

    • CJS (.cjs) → for Node/CommonJS.

    • UMD (.umd.js) → for browsers via <script>.


CDNs (Content Delivery Networks)

A Content Delivery Network (CDN) is a distributed system of servers that deliver web content efficiently to users based on geographic proximity. Many libraries host UMD builds on CDNs so they can be included directly in HTML pages without downloading or installing them locally.

Example of using Lodash from a CDN:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script>
  console.log(_.join(['UMD', 'and', 'CDN'], ' ')); 
  // Output: "UMD and CDN"
</script>

CDNs provide faster access and caching benefits, but relying solely on them in production can introduce dependencies on external services and potential version mismatches. Many production projects prefer installing libraries locally via package managers for greater control.


Historical Evolution of JavaScript Modules

Understanding UMD is easier when viewed in the context of the evolution of JavaScript modules:

  1. Global Scope (Pre-2009): All code shared a single global namespace. Developers used patterns like IIFEs (Immediately Invoked Function Expressions) to encapsulate logic.

  2. CommonJS (2009): Introduced for Node.js, it used require() and module.exports to manage dependencies and exports.

// math.js
function add(a, b) { return a + b; }
module.exports = { add };

// main.js
const math = require('./math');
console.log(math.add(2, 3)); // 5
  1. AMD (2010): Designed for browsers to load modules asynchronously with define(), improving performance for complex web applications.
define(['math'], function(math) {
  console.log(math.add(2, 3));
});
  1. UMD (2010–2012): Unified CommonJS, AMD, and browser globals into a single format, ensuring universal compatibility for libraries.

  2. ES Modules (2015): Introduced native import and export syntax, providing static analysis, tree shaking, and optimized bundling.

// math.mjs
export function add(a, b) { return a + b; }

// main.mjs
import { add } from './math.mjs';
console.log(add(2, 3)); // 5

Today, libraries often provide multiple builds: ES Modules for modern bundlers, CommonJS for Node.js, and UMD for browser inclusion. CDNs commonly host UMD builds to simplify inclusion in web pages.


Conclusion

Libraries and frameworks are foundational to modern JavaScript development. Libraries offer reusable functionality, frameworks provide structure and guidance, and UMD ensures libraries are usable across multiple environments. CDNs enhance accessibility and performance by serving UMD builds directly to browsers.

By understanding these concepts and their historical context, newcomers can make informed choices when including libraries, building applications, or distributing code. UMD, in particular, remains a practical solution for universal compatibility, bridging older module systems and modern ES Modules.


👋 Enjoyed this blog?

Reach out in the comments below or on LinkedIn to let me know what you think of it.

For more updates, do follow me here :)