Why is `export type` necessary with `--isolatedModules`?

11.1k views Asked by At

I don't understand what is happening or why it's made to do this under the hood:

  • If Something is a type, why does export { SomeThing } from "elsewhere" produce an error when --isolatedModules is enabled?

    Conversely why is export { SomeThing } from "elsewhere" not an error if Something is a value, or if --isolatedModules is not enabled?

  • Why does specifying export type { SomeThing } from "elsewhere" fix that error?


(Background information)

There is a new TypeScript feature:

Type-Only Imports and Export

This feature is something most users may never have to think about; however, if you’ve hit issues under --isolatedModules, TypeScript’s transpileModule API, or Babel, this feature might be relevant.

TypeScript 3.8 adds a new syntax for type-only imports and exports.

import type { SomeThing } from "./some-module.js";

export type { SomeThing };

import type only imports declarations to be used for type annotations and declarations. It always gets fully erased, so there’s no remnant of it at runtime. Similarly, export type only provides an export that can be used for type contexts, and is also erased from TypeScript’s output.

I know how to use it and when it's necessary but I don't know why, i.e. apparently when --isolatedModules is enabled then code like the following ...

import type { SomeThing } from "./some-module.js";

export { SomeThing };

... produces a compiler error i.e. ...

Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'.ts(1205)

... and the fix is to use export type { SomeThing } instead of export { SomeThing }.

Incidentally though, apparently import { SomeThing } from "./some-module.js" is OK, doesn't produce an error message, import type { SomeThing } from "./some-module.js" is not required.


By the way this is not the same topic as What is `export type` in Typescript? which is about defining a type and prefixing it with export, in 2017 before this new feature was implemented.

1

There are 1 answers

0
OfirD On BEST ANSWER

There is this Babel Github issue, where it is explained:

In order to determine whether something is a type, we need information about other modules.

import { T } from "./other-module";
export { T }; // Is this a type export?

There's also a great elaborated explanation on this Reddit thread:

type space is TypeScript's domain: all of the types that make up your app. They only exist at compile time. Once the compiler is done, the resulting JavaScript has no types left in it. Value space is basically the opposite: it's the stuff that sticks around and survives into the JS.

type Foo = { bar: string };
const a: Foo = { bar: 'hello' };

Here, Foo is in the type space, everything else is in the value space.

The problem with isolatedModules, is they can't know which space they are working with, since each file is compiled individually. So if you do:

export { Foo } from './bar';

in a file, TypeScript can't know if Foo is in the type space (if it is, it just gets thrown away as it won't be in the resulting JavaScript), or if it is in the value space (ie it's JS, so it needs to be included in the resulting output).