Skip to content

module: allow .ts in node_modules when .d.ts is present#63936

Open
GeoffreyBooth wants to merge 1 commit into
nodejs:mainfrom
GeoffreyBooth:strip-types-twin-dts
Open

module: allow .ts in node_modules when .d.ts is present#63936
GeoffreyBooth wants to merge 1 commit into
nodejs:mainfrom
GeoffreyBooth:strip-types-twin-dts

Conversation

@GeoffreyBooth

Copy link
Copy Markdown
Member

Alternative to #63853 or #63869

Problem

By default, Node.js refuses to strip types from .ts/.mts/.cts files under node_modules, throwing ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING. The restriction protects editor/tsc performance: a dependency that ships raw TypeScript without declarations forces consumers to infer types from its source (“slow types”).

But using the folder as the boundary also blocks legitimate cases where trusted first-party TypeScript ends up under node_modules and does ship declarations:

  • monorepo deploys that copy workspace packages into a real node_modules (pnpm deploy),
  • packages installed from a private registry or a Git URL,
  • globally installed TypeScript CLIs.

Solution

Add an experimental, opt-in flag, --experimental-strip-types-in-node-modules-with-declarations. Under it, a TypeScript file under node_modules is stripped and executed when a co-located declaration sits beside it (foo.d.ts next to foo.ts) — the default layout emitted by tsc --emitDeclarationOnly. The gate is a single stat, and requires type-stripping to be enabled (rejected with --no-strip-types). Otherwise the existing error is thrown.

Each of the blocked workflows already runs a build or publish step, so producing declarations is cheap: the same CI that assembles a pnpm deploy, publishes to a private registry, or packs a global CLI can run tsc --emitDeclarationOnly to emit .d.ts files beside the sources. That one artifact then both satisfies Node’s gate and ships ready-to-consume types, so the raw .ts runs with no separate JavaScript build — which is the thing these users are actually asking for.

It also keeps the TypeScript team’s guarantee intact. The restriction exists to stop a dependency’s raw, undeclared source from dragging down a consumer’s editor and tsc; by tying execution to the presence of a declaration, Node will only run raw .ts from a package that already provides one, so the consumer’s tooling always resolves a .d.ts and never falls back to inferring types across a dependency. The runtime permission is bound to the same artifact that keeps editing fast, rather than to where the code happens to live.

Notes

  • Co-located only. A declaration reachable only through a separate directory (via the exports "types" condition) is not recognized in this initial version; adding that is a small follow-up if the layout proves common.
  • Flagged, but should be unflaggable. It is behind an experimental flag for now to gauge reaction, but the intent is that this should be something that we can enable by default in the future. Since this preserves the gate that requires public registry packages to contain type declarations, it avoids the pollution of the public registry with TypeScript files that lack accompanying .d.ts files.
  • Alternatives considered: module: allow type stripping in node_modules #63853 removes the restriction outright; module: add --experimental-strip-private-modules  #63869 gates on private: true (which can’t cover private-registry packages and doesn’t address editor performance).

@nodejs/typescript @anonrig @marco-ippolito @RyanCavanaugh

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/loaders
  • @nodejs/typescript

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Jun 16, 2026
@GeoffreyBooth GeoffreyBooth added the module Issues and PRs related to the module subsystem. label Jun 16, 2026
By default, Node.js refuses to strip types from `.ts`/`.mts`/`.cts`
files under `node_modules`, throwing
`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`. This protects editor and
`tsc` performance: a dependency that ships raw TypeScript without
declarations forces consumers to infer types from its source. But the
folder-based rule also blocks legitimate cases where trusted first-party
TypeScript ends up under `node_modules`, such as monorepo deploys (`pnpm
deploy`), packages from a private registry or a Git URL, and globally
installed TypeScript CLIs.

Add an experimental, opt-in flag
`--experimental-strip-types-in-node-modules-with-declarations`. Under
it, a TypeScript file under `node_modules` is stripped and executed when
a co-located declaration file sits beside it (e.g. `foo.d.ts` next to
`foo.ts`), the default layout emitted by `tsc --emitDeclarationOnly`.
The declaration's presence signals that the author pre-computed the type
boundaries downstream tooling relies on, so editors read declarations
instead of inferring from raw source. The check is a single `stat`, and
the flag is rejected unless type-stripping is enabled.
Refs: nodejs#63853
Refs: nodejs#63869

Signed-off-by: Geoffrey Booth <webadmin@geoffreybooth.com>
@GeoffreyBooth GeoffreyBooth force-pushed the strip-types-twin-dts branch from e0bbcf0 to 1826070 Compare June 16, 2026 00:36
@anonrig

anonrig commented Jun 16, 2026

Copy link
Copy Markdown
Member

I think this is a far better solution than any alternatives. Thank you.

@anonrig anonrig added the request-ci Add this label to start a Jenkins CI on a PR. label Jun 16, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jun 16, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. module Issues and PRs related to the module subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants