Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the real use cases of type-only imports that justify the added verbosity?

When I upgraded to the latest version of TypeScript and found out about type-only imports, I thought it was super cool and started using it everywhere.

After a while setting up type-only imports, I soon realised I was getting quite more verbosity and "dirty code" I had expected.

Without type-only imports:

import { SomeType, someFunction, SomeClassToInstantiate } from '@my-app/lib1';
import { OtherType, otherFunction, OtherClassToInstantiate } from '@my-app/lib2';

With type-only imports:

import type { SomeType } from '@my-app/lib1';
import { someFunction, SomeClassToInstantiate } from '@my-app/lib1';
import type { OtherType } from '@my-app/lib2';
import { otherFunction, OtherClassToInstantiate } from '@my-app/lib2';

Basically, a lot of my imports get duplicated, and it's difficult to track whether I am importing everything in the correct way (the compiler flags if I am importing as type something that I am physically using in the file - but the opposite doesn't hold, I didn't find any tool to flag that an import is only used as type and should be switched to type-only import).

Maybe I am noticing this problem more because I am using NX and a lot of my app's code comes from the libs' barrel files; i.e. it often happens that both a type and a non-type have to be imported from the same module.

So I was wondering, what actual advantages do I get from using type-only imports everywhere like that? Are there instead specific circumstances in which it's definitely helpful to use it, and it can be bypassed in all the other cases?

If the answer to my previous question is that it should always used when possible, do you know any linting rule to enforce type imports when they can be used?

I don't like the confusion of having nearly double the imports than before, without even knowing that they are consistently split across regular / type-only imports everywhere.

like image 484
Adriano di Lauro Avatar asked Nov 16 '25 21:11

Adriano di Lauro


2 Answers

Are there instead specific circumstances in which it's definitely helpful to use it, and it can be bypassed in all the other cases?

Although it does not hurt to use type-only imports unconditionally (for consistency's sake), there are, indeed, specific circumstances where they are required. The following table compares different option combinations in terms of use cases:

isolatedModules/preserveValueImports true false
true Type-only imports are required as single-file processing lacks full type system information to elide type imports.
Example
Type-only exports are required if exporting types.
Example
false Preferencial, type-only imports will be elided Preferencial, type-only imports will be elided

The most common use case for them is when both isolatedModules and preserveValueImports are enabled, which is explicitly documented:

When combined with isolatedModules: imported types must be marked as type-only because compilers that process single files at a time have no way of knowing whether imports are values that appear unused, or a type that must be removed in order to avoid a runtime crash.

Another use case is when isolatedModules module is enabled (regardless of the preserveValueImports option), and you are exporting types (imported or not), which is also documented:

Single-file transpilers don’t know whether someType produces a value or not, so it’s an error to export a name that only refers to a type.

If your config has preserveValueImports enabled, type-only imports are also useful to force the elision of imports that are only used in type positions that would not be a runtime error if preserved (in the following example, import type will allow the compiler to elide the import entirely):

import { ESLint } from "eslint"; // ESLint is a class
let esl: ESLint;
export { esl };

As for linting, @typescript-eslint/eslint-plugin for ESLint has 2 rules that govern the usage of type-only imports and exports:

  • consistent-type-imports
  • consistent-type-exports

On an off-note, since 4.5, TypeScript has type modifiers on import names that were added specifically to address the verbosity of type-only imports, so you no longer need to split imports:

import { someFunction, SomeClassToInstantiate, type SomeType } from '@my-app/lib1';
like image 126
Oleg Valter is with Ukraine Avatar answered Nov 19 '25 12:11

Oleg Valter is with Ukraine


The type-only imports/exports is a feature proposed to solve a specific problem/trouble, not just providing convenience.

There were two conflicting cases to solve:

Case 1: I want to keep their side-effects, so do NOT remove it!

// Input
import { OnlyUsedAsType } from "sideEffectMod";
import { UsedAsValue } from "basicMod";

declare const a: OnlyUsedAsType;
const b = UsedAsValue;

// Output
// <-- `sideEffectMod` is removed! -->
import { UsedAsValue } from "basicMod";

const b = UsedAsValue;

Case 2: For some reason isolatedModules is on, so please DO help me remove used imports!

import { SomeType } from "someModule"; // Cannot assume it's a type.
export { SomeType };                   // Cannot assume it's a type.
let x: SomeType | undefined;

export { SomeOtherType } from "someModule"; // Cannot assume it's a type.

So, they decided to create two things:

  1. A new syntax -- type-only imports/exports, i.e. import type {...}.
  2. An option to preserve side-effects, i.e. importsNotUsedAsValues.

Now both cases are resolved.

like image 39
Rainning Avatar answered Nov 19 '25 10:11

Rainning