export =

Declares that a module's entire export is one value, matching CommonJS module.exports = x.

Signature
export = ident;
  • Added in TypeScript 1.0

Parameters

ParameterTypeDescription
$idententity nameA single identifier naming the class, function, interface, namespace, enum or variable that becomes the module's whole export. It must be a name already declared in the file — an arbitrary expression is not allowed.

Errors

Raises "An export assignment cannot be used in a module with other exported elements" if any `export` declaration coexists with it, and "An export assignment cannot be used in a module with ES module syntax" (or when targeting an ES module format).

Changelog

  • PHP 2.7`esModuleInterop` added: consumers can now import an `export =` module with `import x from "mod"` rather than `import x = require("mod")`.
  • PHP 4.7Under `--module node16`/`nodenext`, `export =` is rejected in ES module files, since ESM has no single-value export slot.
  • PHP 5.0`verbatimModuleSyntax` emits `export = { ... }` verbatim as `module.exports = { ... }` instead of rewriting ES export syntax.

export = X says the module is X, not a namespace containing X. It is the type-system spelling of module.exports = X, and it is how most pre-ESM npm packages actually behave: require("express") returns a callable function, not an object with an express property.

When to reach for it

Nearly always when writing a .d.ts for an existing CommonJS package. If require("pkg") hands back a function or a class directly, export = is the only accurate description; export default would be wrong, because it describes a module with a default property.

It also solves a problem module.exports = cannot: the exported entity may be an interface or a namespace. module.exports = Options fails when Options is type-only, while export = Options is fine.

What trips people up

It is exclusive. One export = and every other export in the file is an error, which is why you attach extra members via declaration merging — a function plus a namespace of the same name — rather than by adding exports.

On the consuming side, whether import x from "mod" works depends on esModuleInterop. With it off, only import x = require("mod") compiles.

Example

Example · typescript
// counter.ts
class Counter {
  constructor(public value = 0) {}
  increment(): this { this.value++; return this; }
}
export = Counter;              // emits: module.exports = Counter;

// app.ts
import Counter = require("./counter");

const c = new Counter(41);
console.log(c.increment().value);
// -> 42

const typed: Counter = c;      // the name works as a type too
console.log(typed.value);
// -> 42

When to use it

  • Write typings for an npm package whose require() returns a function or class directly
  • Describe a module whose sole export is an interface, which module.exports cannot express
  • Author a .cts file under verbatimModuleSyntax that must emit module.exports =
  • Attach option types to a callable export via function/namespace declaration merging

Frequently asked questions

What is the difference between export = and export default?

`export =` makes the module itself equal to one value, matching `module.exports = x` in CommonJS; `export default` creates a module object with a `default` property. They emit different JavaScript, and a consumer importing an `export =` module with default syntax needs `esModuleInterop` for the two to line up.

Why does export = say it cannot be used in a module with other exported elements?

Because the export assignment replaces the entire export object, so no other export can survive. Remove the other `export` keywords and, if you need extra members, merge them into the exported entity with a same-named namespace declaration.

Can I use export = in an ES module?

No. TypeScript rejects `export =` in any file it treats as ESM — a `.mts` file, or a `.ts` file in a package with `"type": "module"` under `--module node16`/`nodenext`. Use `export default` there, and reserve `export =` for CommonJS files and declaration files describing CommonJS packages.

More examples

A callable export with attached types, via declaration merging

Because `export =` allows exactly one exported entity, the options interface and the `defaults` constant ride along inside a namespace that merges with the function of the same name. Consumers reach them as `slugify.Options` and `slugify.defaults` — the shape real CommonJS packages have, where the module is a function that also carries properties.

Example · typescript
// node_modules/slugify/index.d.ts
declare function slugify(input: string, options?: slugify.Options): string;

declare namespace slugify {
  interface Options {
    separator?: string;
    lower?: boolean;
  }
  const defaults: Required<Options>;
}

export = slugify;

// app.ts
import slugify = require("slugify");

const opts: slugify.Options = { separator: "_", lower: true };
console.log(slugify("Hello World", opts));
// -> "hello_world"
console.log(slugify.defaults.separator);
// -> "-"

What esModuleInterop changes for consumers

The same `export =` module is imported three ways. With esModuleInterop off, only the `import =` form compiles. With it on, TypeScript synthesises a default export and the ordinary default import works. The named import never works, because `export =` publishes no named members at all — a distinction that catches people who assume the export name is also a member name.

Example · typescript
// mod.ts
interface Options { retries: number }
function connect(o: Options): string { return `retries=${o.retries}`; }
declare namespace connect { export { Options }; }
export = connect;

// consumer-a.ts  — esModuleInterop: false
import connect = require("./mod");
console.log(connect({ retries: 3 }));
// -> "retries=3"

// consumer-b.ts  — esModuleInterop: true
import connect2 from "./mod";
console.log(connect2({ retries: 5 }));
// -> "retries=5"

// consumer-c.ts — always an error: the module has no named export 'connect'
// import { connect as bad } from "./mod";
// tsc: Module '"./mod"' has no exported member 'connect'.

Discussion

  • Be the first to comment on this lesson.