TS1446: '{0}' resolves to a type-only declaration and must be imported using a type-only import when 'preserveValueImports' and 'isolatedModules' are both enabled

TypeScript is a programming language that builds on JavaScript by introducing static types. A type is a way of describing the structure and behavior of your data to help ensure that your code is safe and predictable. For example, when you use a type in TypeScript, you are telling the compiler exactly what kinds of values a variable or function is allowed to work with. This makes your code easier to maintain and less prone to runtime errors.

If you're learning TypeScript or want to improve your coding skills using AI tools, I highly recommend following my blog for more in-depth articles and tutorials. Alternatively, you can use AI tools like GPTeach to guide your learning process and improve faster.

For this article, we'll also explore an essential feature of TypeScript: types (structures that define the data your code works with). Types allow you to explicitly state what kind of values a variable can hold or what shape a specific object should follow. This reduces errors and makes your code clearer. Let’s consider a brief example of how types work:

type User = {
  id: number; // id must be a number
  name: string; // name must be a string
};

const user: User = {
  id: 123,
  name: "John",
};

// Error: "age" is not defined in the User type!
user.age = 25;

Now, let's focus on the main topic: TS1446: '{0}' resolves to a type-only declaration and must be imported using a type-only import when 'preserveValueImports' and 'isolatedModules' are both enabled.


What is TS1446?

When working with TypeScript in certain strict configurations, you might encounter the error:

TS1446: '{0}' resolves to a type-only declaration and must be imported using a type-only import when 'preserveValueImports' and 'isolatedModules' are both enabled.

This error occurs when your TypeScript project is configured to use the preserveValueImports (ensures that unused value imports are preserved in the final code output) and isolatedModules (forces each file to be treated as its own module) compiler options. These options, while useful for ensuring correctness and optimization, also require you to explicitly distinguish between importing types and importing values.

If you're trying to use a type (e.g., interface, type, or enums) but haven't declared it as a "type-only" import, TypeScript will throw the TS1446 error. Let’s break this down into human-friendly terms.


Understanding the TS1446 Error

When TypeScript compiles your code under these strict options, it assumes that every import without a type modifier is importing a value (something that exists during runtime, like functions, variables, or classes). However, types only exist during compile time—they don't exist in JavaScript's runtime. Hence, TypeScript introduces the concept of type-only imports to differentiate pure types from runtime imports.

For example, imagine you define a User type in an external module and try to import it like this:

// user.ts
export type User = {
  id: number;
  name: string;
};

// main.ts
import { User } from "./user";

const printUser = (user: User): void => {
  console.log(user.name);
};

If your tsconfig.json has preserveValueImports and isolatedModules enabled, the compiler will throw the TS1446 error because User is a type-only declaration and must be imported explicitly as a type.


Fixing TS1446

The way to fix TS1446 is simple: use the type keyword to perform a type-only import. Here's how to modify the code above to conform to the compiler rules:

// Correct: Explicit type-only import
import type { User } from "./user";

const printUser = (user: User): void => {
  console.log(user.name);
};

Now TypeScript knows that User is a compile-time-only concept, and no unnecessary imports will appear in the compiled JavaScript.


Important to Know!

  1. Type-only imports are for compile-time: The type keyword ensures TypeScript recognizes the import as something that only exists in TypeScript, not in the emitted JavaScript.
  2. Value imports are for runtime: Anything like classes or functions must still be imported without the type keyword.
  3. Combining imports: You can combine runtime and type-only imports if needed:
import { SomeClass } from "./module";
import type { SomeType } from "./module";

Frequently Asked Questions (FAQ)

What is the difference between import and import type?

import is used for importing values (like functions, classes, or variables) that exist in runtime. import type is used exclusively for importing types or interfaces that only exist during compile time.

Why does TS1446 happen only in certain configurations?

TS1446 occurs when both preserveValueImports and isolatedModules are enabled in your tsconfig.json. These settings force strict separation of runtime and compile-time concepts, helping you produce efficient and optimized JavaScript code.


Important to Know!

  • Prerequisites for solving TS1446:
    1. Ensure your tsconfig.json enables strict typing rules (preserveValueImports and isolatedModules).
    2. Explicitly use type for type-only imports whenever needed.

A Quick Recap

To recap, if you encounter TS1446: '{0}' resolves to a type-only declaration and must be imported using a type-only import when 'preserveValueImports' and 'isolatedModules' are both enabled., it means you need to differentiate between types (compile-time) and values (runtime). Always use import type when you're importing a type-only declaration.

By understanding the guidelines above, you'll navigate this error smoothly and produce cleaner, optimized TypeScript code. Remember to subscribe to my blog or use tools like GPTeach to master TypeScript step by step!