Search code examples
javascriptnode-moduleses6-modulesamdumd

What is the Universal Module Definition (UMD)?


I've found the(?) github repository for UMD and it comes with this description (emphasis mine):

UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere.

...

This repository formalizes the design and implementation of the Universal Module Definition (UMD) API for JavaScript modules. These are modules which are capable of working everywhere, be it in the client, on the server or elsewhere.

So far, this is straightforward. At least I think it is. Node.JS uses CommonJS by default. The browser uses globals by default. I think it's saying UMD works regardless of your module system; you can build your project once instead of once per module system.

But everything that follows confuses me:

The UMD pattern typically attempts to offer compatibility with the most popular script loaders of the day (e.g RequireJS amongst others)...

Doesn't this contradict the previous paragraph? Does UMD work everywhere or just in the most popular environments?

It goes on to list at least nine "variations" to choose from. Why would there be any variations if this is meant to be universal? How do I determine which one is right for my situation?

The readme is written as though I already know the answers to these questions, but I'm trying to learn what UMD is for and when to use it.


Note: This similar question is asking about other module systems, but not UMD.


Solution

  • The repository aims to formalize the definition of universal modules. It does not provide a universal definition. The standard was written at the time because everyone defined their universal modules differently - there were some common approaches, with varying support for different environments, but nothing really established.

    A universal module, as you already quoted, works in multiple environments. Before universal modules, libraries would distribute separate files for the different environments, e.g. library.amd.js, library.common.js and library.global.js. This was a major hassle for maintainers, since it required the usage of a build tool (which JavaScript does did generally not need) and extra documentation. So the aim was to define a universal module, a single file, which was all that was needed for the author to write and distribute and for the user to (down)load. Notice this was before package managers and repositories were common (or: when they started to become more popular, and you had to change your library into a module to support them).

    Yet, there were still variations. Modules written for web browsers did not need to consider supporting the commonjs format, as they wouldn't work in nodejs anyway. Modules that had no dependencies wouldn't need a UMD pattern that supported require. Modules that did not use circular dependencies wouldn't need a pattern that supports a mutable exports object. You can find the explanation of these differences, and their trade-offs, in the comments documenting the patterns in the UMD repository. A library author could go there, read through them, and pick the right one.


    Of course, all of that is history. Today, build tools are ubiquitous. You write your code as modern ES modules, and let your bundler figure out the rest. For library authors, you can just distribute your package as a bundle of esm files.

    However, for frontend libraries, you still offer a single file for convenience, that users can download (from a CDN) and directly embed in their web pages. This still commonly employs a UMD pattern, it's just no longer written/copied by the library author into their source code, but added automatically by the transpiler/bundler.

    And similarly, for backend/universal libraries that are supposed to work in node.js, you still also distribute a commonjs module build via npm to support all the users who still use a legacy version of node.js (and don't want/need to employ a transpiler themselves). This is less common nowadays for new libraries, but existing ones try hard to stay backwards-compatible and not cause applications to break.