Question

TypeScript: Ignore node_modules Errors with skipLibCheck and lib

typescriptnode.jstsctsconfig

Question

I am running TypeScript in watch mode with this command:

.\node_modules\.bin\tsc.cmd --watch -p .\tsconfig.json

TypeScript reports errors from node_modules/@types/node/index.d.ts:

error TS2304: Cannot find name 'Map'.
error TS2304: Cannot find name 'Set'.
error TS2304: Cannot find name 'Symbol'.
error TS2304: Cannot find name 'WeakMap'.
error TS2304: Cannot find name 'WeakSet'.

I added node_modules to the exclude list in my tsconfig.json, but the errors still appear:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "sourceMap": true,
    "strict": false,
    "noImplicitAny": false,
    "strictPropertyInitialization": false,
    "esModuleInterop": true
  },
  "include": ["src/*"],
  "exclude": [
    "node_modules",
    "./node_modules",
    "./node_modules/*",
    "./node_modules/@types/node/index.d.ts"
  ]
}

What am I doing wrong, and how can I stop or fix these errors from Node type declarations? I am using VS Code and TypeScript 2.9.2.

Short Answer

exclude controls which source files TypeScript discovers as project roots; it does not prevent TypeScript from loading declaration files required by an import or by installed @types packages. The Map, Set, and Symbol errors usually mean the configured JavaScript library is too old. Add an ES2015 library, use skipLibCheck when appropriate, or restrict automatically included type packages with types.

Concept

TypeScript compiles more than the .ts files you wrote. It also reads declaration files (.d.ts) that describe JavaScript libraries and packages.

For example, @types/node describes Node.js APIs. Its declarations use built-in JavaScript types such as Map, Set, Symbol, WeakMap, and WeakSet. Those types were introduced with ES2015 (also called ES6).

When your configuration targets ES5, TypeScript may use only ES5's default standard-library declarations unless you explicitly choose additional libraries. As a result, TypeScript can read @types/node but fail because the ES2015 names that it refers to are unavailable.

exclude is often misunderstood:

  • It stops TypeScript from automatically discovering matching files as root input files.
  • It does not stop TypeScript from following dependencies.
  • It does not reliably remove type declarations brought in through imports, triple-slash references, or automatically discovered @types packages.

This behavior is necessary: if a source file imports a package, TypeScript needs that package's type declarations to type-check the import correctly.

Mental Model

Think of include and exclude as the list of documents placed on a desk at the start of a research task.

If one of those documents cites another book, TypeScript may still open that book to understand the citation. exclude only says, “Do not place this book on the desk initially.” It does not say, “Never open this book if another document depends on it.”

The lib option is the built-in dictionary TypeScript can use. With only an ES5 dictionary, words such as Map and Symbol are missing. Adding ES2015 gives TypeScript the definitions it needs.

Take Quiz

Syntax and Examples

Use lib to choose the built-in JavaScript and environment APIs available during type-checking.

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2015", "dom"],
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

What these options mean:

  • target: "es5" emits older ES5-compatible JavaScript syntax.
  • lib: ["es2015", "dom"] makes ES2015 built-ins such as Map, Set, and Symbol available to the type checker, while also retaining browser types such as window and document.
  • skipLibCheck: true skips type checking inside .d.ts declaration files, including many files in node_modules.
  • include: ["src/**/*.ts"] includes TypeScript files in src and all of its nested directories.

If this is a Node-only project rather than a browser project, omit "dom":

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2015"],
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"]
}

skipLibCheck does not remove the declarations. TypeScript still uses them to understand package APIs; it simply avoids reporting errors found while checking the declaration files themselves.

Step by Step Execution

Consider this code:

// src/cache.ts
const cache = new Map<string, number>();
cache.set("visits", 1);

And this configuration:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2015"]
  },
  "include": ["src/**/*.ts"]
}

TypeScript processes it roughly as follows:

  1. It finds src/cache.ts because it matches src/**/*.ts.
  2. It loads the ES2015 library declarations because of "lib": ["es2015"].
  3. Those declarations define the generic Map<K, V> type and its methods.
  4. It checks that "visits" is a string and 1 is a number.
  5. Because the target is ES5, TypeScript emits ES5-style JavaScript where possible.

If lib did not include ES2015, step 2 would not provide the Map definition. The same missing-name issue can then appear in your own code or in @types/node.

If @types/node is installed, TypeScript can automatically include it. Its declarations refer to ES2015 APIs, so adding the ES2015 library resolves the underlying missing-name errors.

Real World Use Cases

  • Older browser output: A web app may emit ES5 JavaScript for older environments while still using ES2015 type definitions. Runtime support may require polyfills.
  • Node.js applications: Server code commonly installs @types/node for types such as process, Buffer, and Node modules. Its declarations require suitable JavaScript library types.
  • Large dependency trees: Libraries may ship imperfect or version-mismatched .d.ts files. skipLibCheck can keep builds focused on application code.
  • Monorepos: Different packages can need different ambient types. The types option prevents unrelated @types packages from being included automatically.
  • Build performance: Skipping declaration-file checking can noticeably reduce checking time in projects with many dependencies.
Take Quiz

Real Codebase Usage

A common application configuration uses a modern target and skips third-party declaration checking:

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

When a project needs Node types explicitly, teams often make that choice visible:

{
  "compilerOptions": {
    "types": ["node"],
    "skipLibCheck": true
  }
}

The types list is a whitelist for automatically included @types packages. For example, a browser-only project that should not receive Node globals can use:

{
  "compilerOptions": {
    "types": []
  }
}

Use types: [] carefully. It can remove useful package-provided global types. It is not needed merely because a package exists in node_modules.

In maintained codebases, the preferred order is usually:

  1. Use compatible versions of TypeScript and @types packages.
  2. Configure the correct target and lib values.
  3. Enable skipLibCheck if declaration checking is not valuable for the project.
  4. Restrict types only when unwanted global type packages are actually being loaded.

Common Mistakes

Assuming exclude blocks dependencies

This does not guarantee that Node declarations will never be loaded:

{
  "exclude": ["node_modules"]
}

TypeScript can still load a declaration file when your program or an installed type package requires it. Use lib, types, or skipLibCheck based on the actual problem.

Adding many duplicate exclude patterns

These patterns are redundant:

"exclude": [
  "node_modules",
  "./node_modules",
  "./node_modules/*"
]

Usually this is enough:

"exclude": ["node_modules"]

Using src/* when nested files should compile

src/* matches only direct children of src. It does not match src/features/login.ts.

"include": ["src/*"]

Prefer:

"include": ["src/**/*.ts"]

Treating skipLibCheck as a runtime polyfill

skipLibCheck only changes type checking. It does not create Map in an old browser. If your runtime lacks ES2015 APIs, provide an appropriate polyfill or choose a supported runtime.

Removing Node types when Node APIs are used

Setting "types": [] can hide Node globals such as process and Node module declarations. Do not use it if your application needs Node typings.

Using an incompatible old toolchain

TypeScript 2.9.2 is an old version. If possible, upgrade TypeScript and keep @types/node compatible with the TypeScript version and the Node.js version your project supports. When upgrading is not possible, use a compatible older @types/node release.

Comparisons

OptionWhat it controlsBest useWhat it does not do
excludeFiles discovered as root project inputsAvoid compiling generated folders or test outputBlock imported or referenced declarations
includeFiles TypeScript initially searches forDefine the application's source foldersControl all dependency types
libBuilt-in JavaScript and environment type definitionsFix missing Map, Set, Promise, document, and similar namesAdd runtime polyfills
skipLibCheckChecking inside .d.ts filesAvoid third-party declaration-file errors and speed up buildsValidate every dependency declaration
typesAutomatically included @types packagesLimit global type packages, such as Node or JestStop types explicitly imported by source code

target and lib are related but separate:

SettingPrimary effect
targetJavaScript syntax TypeScript emits
libAPIs TypeScript assumes exist for type checking

It is valid to emit ES5 while using lib: ["es2015"], but your actual runtime must support ES2015 APIs or receive polyfills.

Take Quiz

Cheat Sheet

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2015", "dom"],
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}
  • Missing Map, Set, Symbol, WeakMap, or WeakSet usually means an ES2015 library definition is missing.
  • Add "lib": ["es2015"] for Node projects.
  • Add "dom" too for browser projects that use window, document, or DOM APIs.
  • Use skipLibCheck: true to skip errors inside .d.ts files.
  • exclude does not prevent TypeScript from loading declarations needed by dependencies.
  • Use src/**/*.ts, not src/*, when source files can be in nested folders.
  • Use types: ["node"] to explicitly include Node globals, or types: [] to disable automatic @types inclusion.
  • Type checking does not provide runtime support; add polyfills if the target runtime lacks an API.

FAQ

Why does TypeScript check files in node_modules even when they are excluded?

Because exclude affects initial file discovery, not dependency resolution. TypeScript still loads declarations needed to understand imported packages and automatically included type packages.

How do I fix Cannot find name 'Map' in TypeScript?

Add an ES2015-or-newer library to compilerOptions.lib, such as "lib": ["es2015", "dom"] for a browser project or "lib": ["es2015"] for a Node-only project.

Does skipLibCheck ignore all errors from node_modules?

It skips checking errors inside declaration files (.d.ts). It does not stop TypeScript from using those declarations, and your own code can still receive errors when it uses a library incorrectly.

Should I use skipLibCheck: true?

It is widely used in application projects to reduce build noise and improve speed. If you publish a TypeScript library or need maximum dependency type validation, consider leaving it off in at least one validation build.

Why is src/* not including my subfolders?

The * wildcard matches one path segment. Use src/**/*.ts to include .ts files under src recursively.

Can I target ES5 and use Map?

Yes, TypeScript can type-check Map with lib: ["es2015"] while emitting ES5 syntax. However, the runtime must provide Map, either natively or through a polyfill.

How can I stop @types/node from being automatically included?

Use the types compiler option. "types": [] disables automatic @types inclusion, while "types": ["node"] explicitly includes Node types. Do not disable Node types if your code needs Node APIs.

Is TypeScript 2.9.2 still a good version to use?

It is an older release. Upgrading TypeScript is recommended when project constraints allow it, especially when using current versions of @types packages.

Take Quiz

Mini Project

Description

Configure a small TypeScript project that uses an ES2015 Map while emitting ES5 JavaScript. The project demonstrates that compiler output level, type-library availability, and runtime API support are separate concerns.

Goal

Create a working ES5-targeted TypeScript program that can type-check Map without errors from library declarations.

Requirements

Use target set to es5. Add the ES2015 type library through lib. Include TypeScript files recursively from src. Enable skipLibCheck to avoid checking third-party declaration files. Create a program that stores and reads a value using Map.

Take Quiz

Keep learning

Related questions

View more