Skip to content
Theory · A TypeScript project · lesson 3 of 10

import type

Sometimes the next room needs not the thing itself but only its size. To sew a cover for a sofa, nobody carries the sofa around the house: a slip of paper with its measurements is enough. The slip is needed while the cover is being sewn, and once the cover is ready, the slip is thrown away.

Types in modules are such measurements. Say a program has three files:

  • user.ts exports an interface User with a first and a last name;
  • format.ts exports a function formatName(user: User): string that joins the first and last name;
  • main.ts takes both the interface and the function.

The function formatName works at run time: it is called and returns a string. The interface User is needed only by tsc, to check the code. Here is user.ts:

TypeScript
export interface User {
    first: string;
    last: string;
}

Importing only types

For measurements TypeScript has a separate form, a type-only import: import type { User } from "./user.js";. Let's take it apart:

  • import type is an import that brings only types;
  • { User } holds the type names, and there may be several;
  • from "./user.js" names the module that exports those types.

import type is used for interfaces and type aliases. You can take a class this way too, but then it is only a type: you cannot create an object with new.

Let's see what tsc does with a file main.ts that has both kinds of import. Press "Next step" and watch which lines make it into the JavaScript.

LiveWhat tsc leaves of a modulestep 1 of 6

The import type line is gone entirely: it brought only the type User.

The highlight shows a TypeScript line and the JavaScript line it became

What is left in JavaScript

After compilation, main.js:

  • has no import type line: it is gone entirely;
  • has no interfaces or type aliases such as Badge;
  • has no annotations on parameters, results or variables;
  • keeps functions, classes and enum: they work at run time;
  • keeps ordinary value imports such as import { formatName }.

Here the measurement picture stops working: someone has to throw the slip away, while tsc erases the types from the JavaScript on its own.

One more detail: main.js has no "use strict"; line at the top. A JavaScript module always runs in strict mode anyway, so tsc does not add it.

Why write type

The compiler also removes an ordinary import import { User } from "./user.js" when User appears in the file only as a type. But to find that out, it has to look into user.ts and check every place with that name. import type writes the answer right in the line, and that gives three things:

  1. A reader of the code sees at once that the line will not survive into JavaScript.
  2. tsc holds you to the promise: a name from import type cannot be used as a value.
  3. A tool that sees one file knows the line can be removed. That is how node main.ts works: it cuts out the types without looking into other files. It keeps an ordinary import of User, and at run time Node looks for a value User the module does not have: the program stops with an error.

The second point in code: a function was taken with import type and then called.

TypeScript
import type { formatName } from "./format.js";

const text = formatName({ first: "Anna", last: "Ivanova" }); // ✗
TEXT
error TS1361: 'formatName' cannot be used as a value because it was imported using 'import type'.

tsc reminds you: such an import disappears from the JavaScript, and main.js will have no function formatName.

Interviewers often ask how import type differs from an ordinary import. The answer: import type brings only types and disappears from the JavaScript entirely, while a value import stays and loads the module at run time.

The .js extension in the path

tsc does not change the path after from: the string "./format.js" reaches main.js as it is. At run time main.js loads the neighboring file format.js, the one tsc writes from format.ts. That is why the path in the source names the future .js file. While checking types, tsc knows that format.ts stands behind ./format.js and reads it.

Common mistake

The path was written without an extension:

TypeScript
import { formatName } from "./format"; // ✗

console.log(formatName({ first: "Anna", last: "Ivanova" }));

tsc main.ts stays silent: with the default settings it finds format.ts by such a path too. But node main.js stops:

TEXT
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../format' imported from .../main.js

Node.js looks for the file exactly by the path in main.js, and there is no file format without an extension: the file next to it is format.js. In place of the dots Node.js prints the full path to the project folder. Add the extension: from "./format.js".

Key points

  • import type { User } from "./user.js" brings only types and disappears from the JavaScript entirely.
  • Every type disappears from the JavaScript: interfaces, aliases, annotations; functions, classes, enum and value imports stay.
  • A name from import type cannot be used as a value: tsc answers with error TS1361.
  • tsc does not change an import path, so the path carries the extension of the future file: ./format.js.