[Typescript] Verbatim Module Syntax Enforces Import Type

Zhentiw發表於2024-08-13

One of the interesting things about verbatimModuleSyntax in TypeScript is that it requires a specific type of import when you're working with types. Let's take a look.

In our tsconfig.json file, we have the verbatimModuleSyntax option set to true:

// inside tsconfig.json
{
  "compilerOptions": {
    ...
    "verbatimModuleSyntax": true
    ...

We're working with two files: index.ts and example.ts.

Inside of example.ts is a type called User:

// inside example.ts

export type User = {
  name: string;
  age: number;
};

In index.ts, we're trying to import User from example.js. However, we have an error:

// inside index.ts

import { User } from './example.js'; // red squiggly line under `User`

// hovering over `User` shows:
User is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.

import type {User} from "./user"

If we start writing import type, we get an autocomplete suggestion for User. This import type syntax is great for working with types when verbatimModuleSyntax is enabled.

This syntax tells TypeScript that imported line should be completely removed from the compiled JavaScript output. That's because types are only a TypeScript thing; they don't exist in JavaScript.

We can see this if we look at the generated index.js file – there are no imports at all:

// inside dist/index.js
export {};

If we were to change our import back to import { User } from "./example.js";, without the type keyword, it would get included in the outputted JavaScript, even though User doesn't exist in example.js:

// inside dist/index.js

import { User } from "./example.js";
export {};

This is where the "verbatim" part of "verbatim module syntax" comes in- the import gets included "as is" in the output.

Another Option

We could actually apply the type keyword directly to the named import like this:

import { type User } from "./example.js";

Let's look at an example of how this behavior works.

Inside of example.ts, let's add a console.log statement:

// example.ts

export type User = {
  name: string;
  age: number;
};

console.log("hello!");

When using import { type User }, only the User type is imported.

However, if we have just import { User } without type, any top-level code in example.js like the console log statement will execute immediately. This means that simply by importing index.js, we'd also be running the console.log in example.js.

Using import type allows us to import things from the file, but specify that only the named imports are treated as types without side effects.

相關文章