This article's content
Module types in JavaScript and TypeScript and fixes for common problems

CommonJS modules

CommonJS or CJS modules are the original way to package JavaScript code for Node.js. In Node.js, each file is treated as a separate module. CJS modules are loaded synchronously, that means further code execution is blocked until loading is done. Code is imported dynamic at runtime and not statically ahead of time.

exports and require

const { PI } = Math;
exports.area = (r) => PI * r ** 2;
const circle = require('./circle.js');

ECMAScript modules

Also known as ES modules, ES6 modules and ES2015 modules. ECMAScript modules are the official standard format to package JavaScript code for reuse. Everything inside an ES6 module is private by default, and runs in strict mode (there’s no need for 'use strict').

export and import

function addTwo(num) {
  return num + 2;
}

export { addTwo };
// app.mjs
import { addTwo } from './addTwo.mjs';

// Prints: 6
console.log(addTwo(4));

More syntax variations of import.

import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";

In order to use the import declaration in a source file, the file must be interpreted by the runtime as a module.

In HTML, this is done by adding type="module" to the <script> tag. Modules must be served with the MIME type application/javascript. Regular <script> tags can fetch scripts on other domains but modules are fetched using cross-origin resource sharing (CORS). Modules on different domains must therefore set an appropriate HTTP header, such as Access-Control-Allow-Origin: *. Modules won’t send cookies or other header credentials unless a crossorigin="use-credentials" attribute is added to the <script> tag and the response contains the header Access-Control-Allow-Credentials: true.

In Node this is done by either adding type: "module" to package.json, using mjs as file extension or using the --input-type flag.

Dynamic module imports with import()

Dynamic import(), which does not require scripts of type="module".

Common error:

(node:26444) [ERR_REQUIRE_ESM] Error Plugin: xyz [ERR_REQUIRE_ESM]: require() of ES Module C:\code\xyz\node_modules\somepackage\lib\somepackage.js from C:\code\xyz\dist\index.js not supported.
Instead change the require of somepackage.js in C:\code\xyz\dist\index.js to a dynamic import() which is available in all CommonJS modules.
Cannot use import statement outside a module

This error mainly occurs when you use the import keyword to import a module in Node.js. Or when you omit the type="module" attribute in a script tag.

CommonJS vs. ECMAScript modules

CommonJS and ES6 modules cannot be mixed together, because apart from the different syntax for importing and exporting, CommonJS and ES6 modules execute code at different times when code is being imported: ES6 modules are pre-parsed in order to resolve further imports before code is executed. CommonJS modules load dependencies on demand while executing the code.

// CommonJS modules

// ---------------------------------
// one.js
console.log('running one.js');
const hello = require('./two.js');
console.log(hello);

// ---------------------------------
// two.js
console.log('running two.js');
module.exports = 'Hello from two.js';
running one.js
running two.js
hello from two.js

But with ES2015

// ES2015 modules

// ---------------------------------
// one.js
console.log('running one.js');
import { hello } from './two.js';
console.log(hello);

// ---------------------------------
// two.js
console.log('running two.js');
export const hello = 'Hello from two.js';
running two.js
running one.js
hello from two.js

I would strongly recommend moving to ESM. ESM can still import CommonJS packages, but CommonJS packages cannot import ESM packages synchronously.

ESM is natively supported by Node.js 12 and later.

Other module types

There used to be other module types such as Asynchronous Module Definition (AMD) and Universal Module Definition (UMD) which do not play a big role anymore in modern JavaScript developing.

AMD

The idea with AMD was to bring require known from NodeJS to the browser but loading the modules asynchronously. The following example only works if you have requirejs on your website.

 // add.js
  define(function() {
    return add = function(r) {
      return r + r;
    }
  });


  // index.js
  define(function(require) {
    require('./add');
    add(4); // = 8
  }

UMD

Basically, a UMD module is a JavaScript file that tries to guess at runtime which module system it’s being used in, and then it acts as that kind of module. So you can load the file in a plain <script>, or you can load it from an AMD module loader, or you can load it as a Node.js module, and it will always do something sensible.

Module settings for transpiling

TypeScript config

See a full list of tsconfig compiler options.

"target": "es2019",
"module": "ES2020",
"moduleResolution": "node"

target defines the version of JavaScript that TypeScript will generate. It defaults to ES3, but setting this value to at least ES6 is recommended.

module defines what module system shall be used in the generated JavaScript. For node projects, you very likely want "CommonJS". Changing module also affects moduleResolution.

Module resolution is the process the compiler uses to figure out what an import refers to. moduleResolution can be set to node (default) to use Node.js’ CommonJS implementation or 'node16' or 'nodenext' for Node.js’ ECMAScript Module Support from TypeScript 4.7 onwards.

About Author

Mathias Bothe To my job profile

I am Mathias, born 40 years ago in Heidelberg, Germany. Today I am living in Munich and Stockholm. I am a passionate IT freelancer with more than 15 years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I am founder of bosy.com, creator of the security service platform BosyProtect© and initiator of several other software projects.