ProgrammingFrontend Developer

How does the type system work for imported JavaScript modules in TypeScript? How to type an import if the original JS file does not contain types? What are the nuances and risks of using any in imports?

Pass interviews with Hintsage AI assistant

Answer.

Background:

TypeScript is designed to add static typing to existing JavaScript applications. The question arises — how to type an import when we are connecting a third-party or our own module written in pure JavaScript, where there are no types? In this case, TypeScript uses so-called declaration files (.d.ts), or it may infer types automatically (sometimes incorrectly).

Problem:

If TypeScript cannot find an appropriate type description during import, the variable receives the type any, which means a complete loss of type safety. This can lead to errors that the compiler does not catch and bugs at runtime. Developers often forget to explicitly declare types for imported functions/objects.

Solution:

  1. For your JS modules, you can manually write type declarations (files .d.ts).
  2. Popular libraries often have @types/ packages available.
  3. You can explicitly declare types during import, describing the structure of the required object yourself.
  4. It is recommended to avoid any, and if it cannot be avoided — to minimally limit the scope of its use.

Code example:

// 1. Explicit typing for JS module import import myFunc from './myLib'; declare function myFunc(x: number): boolean; // 2. Import from a JS module for which a file myLib.d.ts was created with export function myFunc(x: number): boolean; import { myFunc } from './myLib'; // 3. Importing a module without typing and explicitly describing the type import * as legacy from './legacy'; const typedLegacy: { runTask: (name: string) => void } = legacy;

Key features:

  • In the absence of declarations, the imported value defaults to the type any, which violates type safety.
  • The best approach is to create .d.ts files for third-party modules/own libraries.
  • If necessary, local declarations of external functions/modules can be made through declare/interfaces.

Trick questions.

Can TypeScript automatically infer types for an imported JS module without declarations?

No, if the file is written in JavaScript, without type declarations TypeScript has to assume any and loses type information, except for trivial cases (export const x = 1;).

Is it possible to "extend" imported types if new fields appear in the JS module?

Only if you update the declaration file (.d.ts). If types are fixed in .d.ts, TypeScript will use them as "truth," and any new fields will remain untyped or lead to an error.

Is it safe to import a third-party JS module into a TypeScript project if there are no @types/ and declarations for it?

No, this greatly reduces safety — all work with the import becomes untyped (any), and the compiler will not give errors even if the module is unavailable or the API has changed. Working with such modules is acceptable only as a temporary solution, with explicit typing or code isolation.

Type errors and anti-patterns

  • Forgetting declaration files (.d.ts) for third-party packages.
  • Blindly trusting implicit any when importing JS modules.
  • Breaking the boundaries of type-safe code.

Real-life example

Negative case

A developer imports a third-party JS library without declarations, confidently using the API, receiving type any. After updating the library, the method signatures change, but no errors occur, only bugs at runtime.

Pros:

  • Quick and easy to connect any JS module.

Cons:

  • No guarantees of type safety, errors remain elusive until runtime.

Positive case

A declaration file .d.ts is created or an @types/ package is added, and the API description strictly matches the original JS module. All imported methods are typed, the IDE provides autocomplete suggestions, and any mismatches show a compilation error.

Pros:

  • Type safety, error warnings before execution.
  • Support for autocompletion and documentation right in the code.

Cons:

  • Time is required to write .d.ts and maintain types with updates to the JS module.