Modules in TypeScript

  1. package.json “type” field
  2. TSConfig Option: module field
  3. Include “.js” extension for custom modules

package.json “type” field

The "type" field defines the module format that Node.js uses for all .js files that have that package.json file as their nearest parent.

If the nearest parent package.json lacks a "type" field, or contains "type": "commonjs", .js files are treated as CommonJS. If the volume root is reached and no package.json is found, .js files are treated as CommonJS. In short, all .js files are treated as CommonJS modules unless "type": "module" is defined in package.json.

Node.js uses CommonJS by default, you need to enable the ES module support by setting "type": "module" in your package.json file.

Suppose there are two .js files in your app and they are both written in ES module, the first util.js:

1
2
3
export function printMessage(msg) {
console.log("The message is: " + msg);
}

And you import it in the main file index.js,

1
2
3
import { printMessage } from "./util.js";

printMessage("Hello");

Then if you try to run this file on Node.js, you might encounter an error.

1
node index.js

Error:

1
2
3
4
import printMessage from './util.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module

There are two ways to fix this problem. You could rewrite your files into CommonJS format, the other way is to add "type": "module" in your package.json file, which is much easier.

1
2
3
4
// package.json
{
"type": "module"
}

Output:

1
The message is: Hello

TypeScript also consult the nearest package.json file to determine the module format when a file with a .js or .ts extension.

TSConfig Option: module field

This is a TypeScript compiler option field which can be found in the tsconfig.json file. It sets what module code is generated.

Allowed values:

  • none
  • commonjs
  • amd
  • umd
  • system
  • es6/es2015
  • es2020
  • es2022
  • esnext
  • node16
  • nodenext

You very likely want "nodenext" for modern Node.js projects, "esnext" for others.

Here’s some example output for this file:

1
2
3
4
// @filename: index.ts
import { valueOfPi } from "./constants";

export const twoPi = valueOfPi * 2;

CommonJS

1
2
3
4
5
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_1 = require("./constants");
exports.twoPi = constants_1.valueOfPi * 2;

UMD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
} else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./constants"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_1 = require("./constants");
exports.twoPi = constants_1.valueOfPi * 2;
});

AMD

1
2
3
4
5
6
7
8
9
10
define(["require", "exports", "./constants"], function (
require,
exports,
constants_1
) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
exports.twoPi = constants_1.valueOfPi * 2;
});

System

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
System.register(["./constants"], function (exports_1, context_1) {
"use strict";
var constants_1, twoPi;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (constants_1_1) {
constants_1 = constants_1_1;
},
],
execute: function () {
exports_1("twoPi", (twoPi = constants_1.valueOfPi * 2));
},
};
});

ESNext

1
2
import { valueOfPi } from "./constants";
export const twoPi = valueOfPi * 2;

ES2015/ES6/ES2020/ES2022

1
2
import { valueOfPi } from "./constants";
export const twoPi = valueOfPi * 2;

node16/nodenext

The emitted JavaScript uses either CommonJS or ES2020 output depending on the file extension and the value of the type setting in the nearest package.json. If the value is module, the format is ES2020, otherwise CommonJS.

None

1
2
3
4
5
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_1 = require("./constants");
exports.twoPi = constants_1.valueOfPi * 2;

Include “.js” extension for custom modules

If you want to use the official TypeScript compiler to generate JavaScript, you have to add the extension because

  1. Node.js refuses to allow extensionless relative imports
  2. TypeScript refuses to add import extensions at compile time

If you want or need to write extensionless imports in TypeScript, you need a bundler or an alternative build tool that can add the .js extensions. For example, you can transpile TypeScript to JavaScript using Babel and babel-plugin-add-import-extension.

However, you’ll still need TypeScript (tsc) itself to do type checking and generate type declaration files, so you’ll need a tsconfig.json with compilerOptions.moduleResolution set to "Bundler".