import and export work as in
JavaScript modules, and may also carry types. This
page covers what TypeScript adds, the compiler’s module settings, and namespaces
(Handbook:
Modules Reference). Whether a file is a module at all is covered in
File structure.
Importing and exporting types
Types can be exported and imported with ordinary syntax, and an import used only as a
type is removed from the output. The type keyword makes that explicit and
guarantees it.
| Form | Since | Meaning |
|---|---|---|
import type { A } from "m" | 3.8 | Types only. The whole statement is removed |
import type D from "m" | 3.8 | A default import, as a type |
import type * as N from "m" | 3.8 | A namespace import, as a type |
import { type A, b } from "m" | 4.5 | Per name |
export type { A } | 3.8 | Exports a name as a type only |
export type * from "m" | 5.0 | Re-exports everything as types |
export type * as N from "m" | 5.0 | Re-exports a namespace, as a type |
import("m").A | 2.9 | A reference to a type in a module, with no import statement |
typeof import("m") | 2.9 | The type of the whole module |
import type { User } from "./user.js";
import { type Role, roles } from "./roles.js";
import type Def, { A } from "./x.js"; // error: not a default and named together
const a: import("./user.js").User = load();
export type { User };
export { type Role };
type cannot be used as a value, but typeof worksWhether the compiler removes other unused imports depends on
verbatimModuleSyntax.
Since 3.8 a file can use await at the top level when
module and target allow it.
Import attributes and defer
Import attributes (5.3) are written with with. The older
assert keyword is an error since 7.0.
import data from "./data.json" with { type: "json" };
type T = import("./lib.js", { with: { "resolution-mode": "import" } }).Thing;
import defer * as feature from "./feature.js";
import defer (5.9) loads a module but runs it only when an export is first
read. Only the namespace form is allowed. It works only with module set
to preserve or esnext, and the compiler does not lower it.
export = and import = require
These forms model CommonJS, where module.exports is replaced as a whole.
export = is an error in a file compiled to ES modules, and it is rejected by
erasableSyntaxOnly. export = can export a type as well as a
value, and import x = require() brings in both.
// a.cts
class Client { url = ""; }
namespace Client { export const version = 1; }
export = Client;
// b.cts
import Client = require("./a.cjs");
const c = new Client();
// a.cjs: module.exports = Client;
// b.cjs: const Client = require("./a.cjs");
In an ES module compiled with a Node.js module mode, import x =
require() becomes a call through createRequire. The form import
A = N.B with no require is an alias for a
namespace member.
Module formats
The module option sets the format of the output. Since 6.0 the default is
esnext. In 7.0 the values amd, umd,
system and none are gone.
| Value | Output | Notes |
|---|---|---|
commonjs | CommonJS | Dynamic import() becomes require() |
es2015, es2020, es2022, esnext | ES modules | es2020 adds import.meta; es2022 adds top-level await |
node16, node18, node20, nodenext | ES module or CommonJS per file | The format depends on the extension and package.json |
preserve | As written | Each import and export keeps its form, for bundlers (5.4) |
Under the Node.js modes, a CommonJS file loading an ES module is an error in
node16 and node18, and allowed in nodenext
(5.8), matching require() of ES modules in recent Node.js. Named imports
from a CommonJS file are not fully checked, because they depend on what Node.js can detect
in the source.
Module resolution
moduleResolution sets how an import specifier is found. The default is
nodenext if module is nodenext,
node16 if module is node16 or node18,
and bundler otherwise. The values node10 and
classic were removed in 7.0.
| Value | Rules |
|---|---|
node16, nodenext | The algorithm of Node.js. An ES module import needs a file extension and does not accept directories. exports and imports in package.json are followed, with the conditions of the importing format |
bundler | Follows exports and imports, and also allows paths without extensions and directories |
Extension substitution
An import is written with the extension of the file that will exist at run time. The compiler then looks for the source of that file:
| Specifier ends in | Files tried, in order |
|---|---|
.js | .ts, .tsx, .d.ts, .js, .jsx |
.mjs | .mts, .d.mts, .mjs |
.cjs | .cts, .d.cts, .cjs |
import { a } from "./util.js"; // finds util.ts while compiling
import { b } from "./util"; // error in an ES module under node16 or nodenext
Two options change this. allowImportingTsExtensions allows imports ending
in .ts, with bundler and noEmit or
emitDeclarationOnly. rewriteRelativeImportExtensions (5.7)
rewrites a relative path ending in a TypeScript extension to the JavaScript one in the
output.
Paths
paths remaps specifiers during type checking only, and does not change
the emitted import. rootDirs treats several folders as one.
customConditions adds conditions for exports. In
package.json, a types condition in exports gives a
declaration file, and typesVersions redirects by compiler version
(see Declaration files).
Namespaces
A namespace groups declarations under a name. Only exported members are
visible outside. Namespaces predate ES modules, which are now preferred
(Handbook:
Namespaces).
NamespaceDeclaration:
[declare] namespace Name {. Name} { {Statement} }
namespace Shapes {
export const unit = 1;
const hidden = 2;
export namespace Inner { export function f() { return hidden; } }
}
namespace A.B.C { export const v = 1; }
A.B.C.v;
import Deep = Shapes.Inner; // an alias
Deep.f();
var Shapes;
(function (Shapes) {
Shapes.unit = 1;
const hidden = 2;
let Inner;
(function (Inner) {
function f() { return hidden; }
Inner.f = f;
})(Inner = Shapes.Inner || (Shapes.Inner = {}));
})(Shapes || (Shapes = {}));
A namespace holding only types produces no code. A namespace with values is rejected
by erasableSyntaxOnly. Namespaces with the same name
merge. The
older form module Name {} is an error since 6.0.
Ambient modules
In a file that is a script, declare module "name" describes a module that
has no TypeScript source. The same syntax in a file that is a module is an
augmentation of an
existing module, and is an error if that module cannot be found.
// shims.d.ts, a script with no top-level import or export
declare module "chart-lib" {
export function draw(data: number[]): void;
}
declare module "legacy-plugin"; // shorthand: every import is any
declare module "*.css" { // pattern: one wildcard
const url: string;
export default url;
}
A declaration file for a library that is also a browser global can add
export as namespace Lib;. In a script, the name is then available, and
modules can use it with allowUmdGlobalAccess.