Implicit module import/export elision · Issue #2812 · microsoft/TypeScript (original) (raw)

Current behavior

The compiler today elides imports and exports that are types or not referenced in a value position in the body of the importing module. See spec sections section 11.2.5 and section 11.2.6 for more details.

This has allowed for using the same syntax to import types and/or namespaces the same way values are imported. This has been convenient but has caused some problems:

  1. It has been consistently a source of confusion for TypeScript users (see issues: Imports left out of module dependencies when not directly assigned or assigned to. #2132, define parameter not generated for a referenced AND used external module class. #2038, and force import #596).
  2. It impedes single-file-transpilation (see Support single-module transpilation mode #2499) as there is no way to know if a re-export (e.g. t in export { t } from "mod") should be elided or not without looking at the whole program

Proposed change

1. Do not elide module imports even if they are only used in type position.

Even if the import is not used, keep the module dependency (i.e. the require() call) to ensure any side effects are maintained.
e.g.:

import { InterfaceFoo } from "foo"; import { ClassBar } from "bar";

var x: InterfaceFoo = new ClassBar();

emits in ES6:

import {} from "foo"; import { ClassBar } from "bar"; var x = new ClassBar();

and ES5/commonjs:

require("foo"); // import but do not capture var bar_1 = require("bar"); var x = new bar_1.ClassBar();

2. Exports with no value side are always elided

This is the existing behavior, an export to an interface or a non-instantiated module will be elided.

e.g.:

interface I {

}

export { I }; // Elided along with the interface declaration

3. A type modifier is required for type-only imports and exports

The type modifier will cause the import to alias the type side of the imported entity, and would indicate the intent for this import statement to be always elided.

import type { IFoo } from "mod1"; // import to "mod1" will be elided

export type { IBar } from "mod2"; // similarly, export statement will be elided

Optionally type can be applied to specific named bindings in export and import declarations, which will result in eliding the import if all its bindings are types.

import {type IFoo, type IBar} from "mod"; // import will be elided

Errors:

import { Interface, Class, Enum } from "foo"; /// Error: import Interface does not have a value side /// and should be marked as type.

export { type } from "bar"; /// Error: export type does not have a value side /// and should be marked as type.

interface IFoo { } export default IFoo; /// Error: export default target must have a value.

Implications to import = / export = syntax

The proposal only affects the new ES6-style import/export syntax. Existing elision rules for import id = require("module") and export = id are not changed.