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.
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
Example
Example
false
The most common use case for them is when both
isolatedModules
andpreserveValueImports
are enabled, which is explicitly documented:Another use case is when
isolatedModules
module is enabled (regardless of thepreserveValueImports
option), and you are exporting types (imported or not), which is also documented: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):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: