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.