A Brief Guide to JavaScript Modules and What They Do

What are JS Modules, and how do they affect consumers of our code?

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Creating React Libraries from Scratch course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Creating React Libraries from Scratch, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Creating React Libraries from Scratch

As we write JavaScript, we add features and our files get bigger and bigger. Naturally, the next step is to separate common code into separate files. In the early days of JavaScript, having separate files meant we needed to have multiple script tags in our HTML. This was tedious because a new script tag would need to be added for each new file we created. Another issue is some files are dependent on other files, so if function in file X, we need to exist before it was called in file Y. These issues led to new module systems being introduced into the JavaScript community, such as AMD or asynchronous module definition and CJS , common JS. Only recently was ESM or the ECMAScript modules introduced as a core JavaScript feature and a final standard for modules. Common JS modules were created to provide synchronous module handling, primarily in Node.js. When a file needs to make code public, the common JS uses module exports syntax. For example, if we were to export an add AB function using common JS, it would look like a function being added and then we use module.exports = add to tell common JS to expose the add function. Importing code uses the require syntax. To use add in a separate file, we would require to point to the add.js file. Lookalike const add = require and then the path to the add file. Asynchronous module definition or AMD is a solution for loaded dependencies as ynchronously, which is popularized in a library called require.js. Code using AMD would look like this. Define is a function that takes an array of dependencies as its first argument. The second argument is a callback, a function that is called after the dependencies are loaded that provides access to loaded modules. AMD is being less used as a result of ECMAScript modules and universal module definitions becoming more popular, but as a library owner, it is important to be aware of it. Universal module definitions or UMD is a combination of common JS and a synchronous module definition. Since common JS is typically seen on the server, an asynchronous module definition is used in the browser, universal module definition was created for code that needs to run in both the web and server environments. Universal module definition is a good universal module system for instances where ECMAScript modules aren't supported, i.e. legacy systems. ECMAScript modules or ESM, or you can also see it as ES6 modules, is a standard and official way of handling modules in JavaScript. It was introduced in ECMAS cript 6 ES6 and provides a sickness module imports. ECMAScript modules has two major keywords, import and export. Let's take a look at a few examples. In ECMAScript modules, we can export code in two ways, as a default export or as a named export. A default export uses the export default syntax. Each file can only have a single default export and is imported by using their name directly. For example, import add from math. Name exports use the export keyword without default. A file can have any number of named exports and are imported by surrounding the name with braces. For example, import, left brace, square, right brace for math. Finally, we have immediately invoked function expressions. I've heard this as iffy or if. This isn't really a module system, but you will find this as an output format in some bundlers. It's a way of wrapping code in a function so that variables don't collide in a global scope, i.e. the window object in web browsers. Immediately invoked function expressions were an early way exposing the libraries in the web browser without clobbering each other. All these different module systems make the question though. What module format should I use and what should I give to my users? While developing our recommend ECMAScript modules. But, inlers can easily work with ECMAScript modules and provide some nice benefits like tree shaking. Move in dead code that doesn't get used in the final output. Which common JS doesn't really support easily. Selecting a module system for code that users consume is a little more difficult. If our code is meant to be used only as a dependency in someone else's project, and not directly in the browser, continue to use ECMAScript modules. As mentioned, ECMAScript module has nice benefits when used through a bundler. If our code is going to be used directly in a web browser, then immediately invoke function expressions or ECMAScript modules are a good choice. ECMAScript modules can only run in modern browsers, so watch out if you need to support older browsers like IE 11. And Node.js using ECMAScript modules or common JS is recommended. Older versions of Node.js don't support ECMAScript modules, but recent changes have recently allowed this. If you need to support older versions of Node.js, then the common JS is a great choice.