This article discusses various JavaScript module formats, including CJS, AMD, UMD, ESM, System, and IIFE, using Rollup examples to illustrate their differences and applications.
Abstract
The article begins by explaining the need for bundlers in modern JavaScript projects to compile small pieces of code into larger and more complex libraries or applications. It then introduces the concept of module formats and lists the formats supported by Rollup, including CJS, AMD, UMD, ESM, System, and IIFE. The article proceeds to explain each format with examples, highlighting their unique features and use cases. The author also discusses the benefits of using ESM, which has gained popularity for both client and server-side development. The article concludes by providing a Rollup configuration file to generate multiple output formats at once.
Bullet points
Modern JavaScript projects require bundlers to compile small pieces of code into larger and more complex libraries or applications.
Module formats are used to arrange bundled code as output.
Rollup supports various module formats, including CJS, AMD, UMD, ESM, System, and IIFE.
CJS is suitable for Node and other ecosystems outside of the web browser, while AMD is used with module loaders like RequireJS.
UMD works as AMD, CJS, and IIFE all in one, while ESM is used for other bundlers and inclusion as a <script type=module> tag in modern browsers.
System is the native format of the SystemJS loader, while IIFE is a self-executing function suitable for inclusion as a <script> tag.
ESM has gained popularity for both client and server-side development since Node started supporting it in version 12.20.0 or later.
The article provides Rollup configuration files to generate multiple output formats at once.
Modern JavaScript projects need a bundler to compile small pieces of code into something larger and more complex, such as a library or application. The popular bundlers are webpack, Rollup, Parcel, RequireJS, and Browserify. They transform JavaScript code into modules that can be loaded as one bundle.
A bundle can be arranged in different formats. In this article, we are going to present real examples of the CJS, AMD, UMD, ESM, System, and IIFE formats.
Bundlers and Formats
This is a standard HTML file that includes a stylesheet on line 5 and a JavaScript file on line 6:
Putting all JavaScript code into one file works for a simple case. As projects scale up, we need to modularize code into independent modules with separate namespaces. In addition to better organization, modularization brings the capabilities of encapsulation, dependency management, and reusability.
This is how a bundler comes into the picture. It is needed to compile small pieces of JavaScript code, along with stylesheets and images, into something larger and more complex, such as a library or application.
cjs (CommonJS) — Suitable for Node and other bundlers (alias: commonjs).
amd (Asynchronous Module Definition) — Used with module loaders like RequireJS.
umd (Universal Module Definition) — Works as amd, cjs, and iife all in one.
es – Keep the bundle as an ES module file. Suitable for other bundlers and inclusion as a <script type=module> tag in modern browsers (alias: esm, module).
system – Native format of the SystemJS loader (alias: systemjs).
iife – A self-executing function suitable for inclusion as a <script> tag. If you want to create a bundle for your application, you probably want to use this.
In this article, we are going to explain these formats with examples.
The Example
Here is an example with four files to be bundled:
index.js
increase.js
decrease.js
others.js
The main entry file is index.js:
On lines 4-6, index.js explicitly lists what are imported from increase.js, decrease.js, and others.js.
This is the imported increase.js:
This is the imported decrease.js:
This is the imported others.js:
For this example, const d in others.js and function divide() in index.js are not used. function power() in index.js is also not used, but it is exported.
ES2015/ES6 introduced the static import and export, which allow static analyzers to build a full tree of dependencies without running code. In addition, this sets up the foundation of tree shaking optimization. According to Wikipedia:
“Tree shaking eliminates unused functions from across the bundle by starting at the entry point and only including functions that may be executed.”
Since this example is written in ES2015 style with specific imports (not import *), the rollup tree shaking process will remove const d and function divide() in all generated formats. function power() is kept since the exported function can be potentially used.
CommonJS (CJS)
CJS is suitable for Node and other ecosystems outside of the web browser. It is widely used on the server side. CJS can be recognized by the use of the require() function and module.exports. require() is a function that can be used to import symbols to the current scope from another module. module.exports is an object that the current module returns when it is required in another module.
CJS modules were designed with server development in mind. Naturally, the API is synchronous. In other words, modules are loaded at the moment and in the order in which they are required inside a source file.
Since CJS is synchronous and not natively recognized by browsers, a CJS module cannot be used on the browser side unless it is packaged with a transpiler. A transpiler, like Babel or Traceur, is a tool that helps to write code in the later versions of JavaScript. If the environment does not support the later version natively, the transpiler compiles them to a supported version.
The following is a Rollup-generated file in CJS format:
We execute this file on the browser side, and it errors out with the message exports is not defined (line 3).
The error can be fixed by including the following code in index.html:
<script>constexports = {};
</script>
Asynchronous Module Definition (AMD)
AMD was born out of CJS to support asynchronous module loading. The main difference between AMD and CJS lies in its support for asynchronous module loading. AMD is used by RequireJS, working on the browser side.
“AMD provides some CJS interoperability. It allows the similar exports and require() interface in the code, although its own define() interface is more basal and preferred.”
The following is a Rollup-generated file in AMD format:
We execute this file on the browser side, and it errors out with the message define is not a function (line 1).
The error can be fixed by including require.js in index.html:
UMD is designed to work everywhere — on the server side and the browser side. It attempts to offer compatibility with the most popular script loaders of the day, such as RequireJS. In many cases, it uses AMD as a base with special casing added to handle CJS compatibility. However, the compatibility adds some complexity that makes it complicated to read and write.
The following is a Rollup-generated file in UMD format:
This code works in a browser.
ES2015 Module (ESM)
ESM become the official standard used in JavaScript since ES2015. It is widely used in JavaScript client development, and is also adopted by TypeScript that is a superset with additional types.
Since the version of ^12.20.0 || ^14.13.1 || >=16.0.0, Node starts to support ESM. ESM gains popularity to be used for both clients and servers.
The static import directive can be used to bring modules into the current scope. The dynamic import() is available since ES2020.
The export directive, on the other hand, can be used to explicitly make items public.
The following is a Rollup-generated file in ESM format:
We execute this file on the browser side, and it errors out with the message Uncaught SyntaxError: Unexpected token 'export' (line 45).
The error can be fixed by setting the script tag’s type to be module in index.html:
Immediately Invoked Function Expression (IIFE) Module
As the module’s name suggests, IIFE is a self-executing function that is suitable for inclusion as a <script> tag. We can use this format to create a bundle for an application. It helps us to put things into namespaces to avoid variable collisions and keep code private.
The following is a Rollup-generated file in IIFE format:
This code works in a browser.
Generate Multiple Formats
rollup.config.js is a Rollup configuration file. It is optional but powerful, convenient, and thus recommended.
The following is the output configuration that we have been using to generate multiple output formats at once:
Conclusion
We have explored JavaScript module formats — CJS, AMD, UMD, ESM, System, and IIFE. Among these formats, ESM gains popularity to be used for both clients and servers.
Thanks for reading. I hope this was helpful. You can see my other Medium publications here.
Note: Thank you to Daria Mehra for reviewing this article.