Skip to content

feat: basic typescript support using Node.js typescript API (only strip types and runtime transormation)#20964

Merged
alexander-akait merged 7 commits into
mainfrom
claude/review-typescript-support-Iq6Sj
May 18, 2026
Merged

feat: basic typescript support using Node.js typescript API (only strip types and runtime transormation)#20964
alexander-akait merged 7 commits into
mainfrom
claude/review-typescript-support-Iq6Sj

Conversation

@alexander-akait
Copy link
Copy Markdown
Member

@alexander-akait alexander-akait commented May 15, 2026

fixes #19354

Copilot AI review requested due to automatic review settings May 15, 2026 13:11
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 15, 2026

🦋 Changeset detected

Latest commit: 410928f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread test/configCases/typescript/basic/index.ts Fixed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds experimental built-in TypeScript support behind experiments.typescript, integrating Node.js’s module.stripTypeScriptTypes into webpack’s compilation pipeline and updating defaults/tests/examples accordingly.

Changes:

  • Introduces lib/typescript/TypeScriptPlugin.js and wires it via WebpackOptionsApply when experiments.typescript is enabled.
  • Extends defaults/schema/types/CLI option snapshots to expose experiments.typescript and module.parser.javascript*.typescript, plus default resolution/rules for .ts/.mts/.cts.
  • Adds a comprehensive set of configCases and updates examples/docs to demonstrate the feature.

Reviewed changes

Copilot reviewed 113 out of 117 changed files in this pull request and generated 21 comments.

Show a summary per file
File Description
types.d.ts Updates generated public typings for new experiments.typescript and parser option.
declarations/WebpackOptions.d.ts Updates declared option types for experiments.typescript and parser option.
schemas/WebpackOptions.json Adds schema entries for experiments.typescript and parser typescript option.
test/snapshots/Cli.basictest.js.snap Updates CLI option snapshot to include new flags.
lib/WebpackOptionsApply.js Applies TypeScriptPlugin when experiments.typescript is enabled.
lib/typescript/TypeScriptPlugin.js Implements TypeScript stripping/transform + sourcemap composition via Node API.
lib/javascript/JavascriptParser.js Extends parser options JSDoc to include typescript.
lib/javascript/JavascriptModulesPlugin.js Passes typescript option into JavascriptParser construction.
lib/config/defaults.js Enables experiments.typescript under futureDefaults, adds TS rules and resolve defaults.
test/Defaults.unittest.js Updates defaults snapshots to reflect new typescript defaults/rules/resolve.
.changeset/experimental-typescript-support.md Documents the new experimental feature for release notes.
test/configCases/typescript/basic/webpack.config.js New configCase enabling the experiment for .ts entry.
test/configCases/typescript/basic/tsconfig.json New configCase tsconfig for TS resolution behavior.
test/configCases/typescript/basic/test.filter.js Guards configCase execution based on Node support.
test/configCases/typescript/basic/module.ts TS module input for configCase.
test/configCases/typescript/basic/module-2.ts Additional TS module for resolution coverage.
test/configCases/typescript/basic/module-3.ts Extensionless TS import target.
test/configCases/typescript/basic/module-4.js JS module to ensure mixed JS/TS works.
test/configCases/typescript/basic/components/my-component.ts TS module resolved via tsconfig paths.
test/configCases/typescript/basic/index.ts Exercises TS syntax + sourcemap output.
test/configCases/typescript/type-only-imports/webpack.config.js New configCase for import type stripping.
test/configCases/typescript/type-only-imports/tsconfig.json tsconfig enabling TS extension imports/paths.
test/configCases/typescript/type-only-imports/test.filter.js Node feature guard for the case.
test/configCases/typescript/type-only-imports/types.ts Declares types-only exports.
test/configCases/typescript/type-only-imports/mixed.ts Mixed type/value export coverage.
test/configCases/typescript/type-only-imports/constants.ts Value-only export coverage.
test/configCases/typescript/type-only-imports/index.ts Asserts runtime correctness with type-only imports removed.
test/configCases/typescript/side-effects-and-only-types/webpack.config.js New configCase for type-only module pruning behavior.
test/configCases/typescript/side-effects-and-only-types/tsconfig.json tsconfig for the case.
test/configCases/typescript/side-effects-and-only-types/test.filter.js Node feature guard for the case.
test/configCases/typescript/side-effects-and-only-types/only-types.ts Type-only module.
test/configCases/typescript/side-effects-and-only-types/index.ts Asserts type-only module doesn’t create runtime module.
test/configCases/typescript/require/webpack.config.js New configCase for CJS require + TS.
test/configCases/typescript/require/tsconfig.json tsconfig for CJS-oriented TS behavior.
test/configCases/typescript/require/test.filter.js Node feature guard for the case.
test/configCases/typescript/require/module.ts TS module exported via module.exports.
test/configCases/typescript/require/module-2.ts Additional TS module for resolution coverage.
test/configCases/typescript/require/module-3.ts Extensionless TS require coverage.
test/configCases/typescript/require/module-4.js JS module to validate mixed require graph.
test/configCases/typescript/require/components/my-component.ts TS module resolved via tsconfig paths.
test/configCases/typescript/require/index.ts Asserts CJS require works across TS/JS + paths mapping.
test/configCases/typescript/query-and-hash/webpack.config.js New configCase for ?query#hash on TS imports.
test/configCases/typescript/query-and-hash/tsconfig.json tsconfig for the case.
test/configCases/typescript/query-and-hash/test.filter.js Node feature guard for the case.
test/configCases/typescript/query-and-hash/module.ts TS module imported with query/hash.
test/configCases/typescript/query-and-hash/index.ts Asserts query/hash import executes correctly.
test/configCases/typescript/provided-exports/webpack.config.js New configCase for provided exports with types removed.
test/configCases/typescript/provided-exports/tsconfig.json tsconfig for the case.
test/configCases/typescript/provided-exports/test.filter.js Node feature guard for the case.
test/configCases/typescript/provided-exports/module.ts Mixes value exports + type export to validate export list.
test/configCases/typescript/provided-exports/index.ts Asserts only runtime exports remain.
test/configCases/typescript/namespace/webpack.config.js New configCase for namespace syntax.
test/configCases/typescript/namespace/tsconfig.json tsconfig for the case.
test/configCases/typescript/namespace/test.filter.js Node feature guard for the case.
test/configCases/typescript/namespace/index.ts Asserts namespace transform works at runtime.
test/configCases/typescript/mts-cts/webpack.config.js New configCase for .mts/.cts handling.
test/configCases/typescript/mts-cts/tsconfig.json tsconfig for the case.
test/configCases/typescript/mts-cts/test.filter.js Node feature guard for the case.
test/configCases/typescript/mts-cts/module.mts .mts input coverage.
test/configCases/typescript/mts-cts/module.cts .cts input coverage.
test/configCases/typescript/mts-cts/packages/mts/package.json Package-level "type": "module" for .js ESM behavior.
test/configCases/typescript/mts-cts/packages/mts/module.js ESM JS module in a "type": "module" package.
test/configCases/typescript/mts-cts/packages/cts/package.json Package-level "type": "commonjs" for .js CJS behavior.
test/configCases/typescript/mts-cts/packages/cts/module.js CJS JS module in a "type": "commonjs" package.
test/configCases/typescript/mts-cts/index.ts Validates module concatenation/module count expectations.
test/configCases/typescript/loader-source-map/webpack.config.js New configCase ensuring TS transform composes with loader sourcemaps.
test/configCases/typescript/loader-source-map/tsconfig.json tsconfig for the case.
test/configCases/typescript/loader-source-map/test.filter.js Node feature guard for the case.
test/configCases/typescript/loader-source-map/loader/banner-loader.js Custom loader emitting sourcemap for composition test.
test/configCases/typescript/loader-source-map/data.ts Loader input TS file for sourcemap composition.
test/configCases/typescript/loader-source-map/index.ts Asserts composed sourcemap content is correct.
test/configCases/typescript/imports-field/webpack.config.js New configCase for package.json#imports resolution with TS.
test/configCases/typescript/imports-field/tsconfig.json tsconfig for the case.
test/configCases/typescript/imports-field/test.filter.js Node feature guard for the case.
test/configCases/typescript/imports-field/package.json Defines imports mappings to TS files.
test/configCases/typescript/imports-field/src/module.ts TS module targeted by imports mapping.
test/configCases/typescript/imports-field/index.ts Asserts imports mapping resolves TS and JS correctly.
test/configCases/typescript/experiments-and-ts-loader/webpack.config.js New configCase ensuring ts-loader can coexist with built-in transform.
test/configCases/typescript/experiments-and-ts-loader/tsconfig.json tsconfig for ts-loader case.
test/configCases/typescript/experiments-and-ts-loader/test.filter.js Node feature guard for the case.
test/configCases/typescript/experiments-and-ts-loader/module.ts TS module input.
test/configCases/typescript/experiments-and-ts-loader/index.ts Asserts output works when both pipelines are enabled.
test/configCases/typescript/dynamic-import/webpack.config.js New configCase for dynamic import of TS module.
test/configCases/typescript/dynamic-import/tsconfig.json tsconfig for the case.
test/configCases/typescript/dynamic-import/test.filter.js Node feature guard for the case.
test/configCases/typescript/dynamic-import/async-dep.ts Async TS dependency.
test/configCases/typescript/dynamic-import/index.ts Asserts dynamic import works.
test/configCases/typescript/directory-index-resolution/webpack.config.js New configCase for resolving directory index.ts.
test/configCases/typescript/directory-index-resolution/tsconfig.json tsconfig for the case.
test/configCases/typescript/directory-index-resolution/test.filter.js Node feature guard for the case.
test/configCases/typescript/directory-index-resolution/lib/index.ts Directory index TS file.
test/configCases/typescript/directory-index-resolution/index.ts Asserts ./lib resolves to ./lib/index.ts.
test/configCases/typescript/data-uri/webpack.config.js New configCase for TypeScript data: URI modules.
test/configCases/typescript/data-uri/tsconfig.json tsconfig for the case.
test/configCases/typescript/data-uri/test.filter.js Node feature guard for the case.
test/configCases/typescript/data-uri/index.ts Asserts TS data URI module executes and exports correctly.
test/configCases/typescript/conflict-resolution/webpack.config.js New configCase for .ts vs .js conflict resolution.
test/configCases/typescript/conflict-resolution/tsconfig.json tsconfig for the case.
test/configCases/typescript/conflict-resolution/test.filter.js Node feature guard for the case.
test/configCases/typescript/conflict-resolution/module.ts TS module expected to win resolution.
test/configCases/typescript/conflict-resolution/module.js Competing JS module for resolution test.
test/configCases/typescript/conflict-resolution/index.ts Asserts .ts is preferred over .js.
examples/typescript/webpack.config.js Updates example to use experiments.typescript + ForkTsChecker.
examples/typescript/tsconfig.json Updates example tsconfig to match new workflow (erasableSyntaxOnly, etc.).
examples/typescript/test.filter.js Updates example test filter to Node stripTypeScriptTypes availability.
examples/typescript/template.md Updates example docs to describe built-in TS support.
examples/typescript/index.ts Updates example code to demonstrate type-only import usage.
examples/typescript/greeter.ts Adds a TS module used by the updated example.
examples/typescript-non-erasable/webpack.config.js Adds companion example using ts-loader for non-erasable syntax.
examples/typescript-non-erasable/tsconfig.json Adds tsconfig for non-erasable example.
examples/typescript-non-erasable/test.filter.js Adds test filter for non-erasable example.
examples/typescript-non-erasable/template.md Adds docs explaining when to use the non-erasable example.
examples/typescript-non-erasable/README.md Adds generated README content for the new example.
examples/typescript-non-erasable/index.ts Adds non-erasable TS syntax sample.
examples/typescript-non-erasable/example.js Adds example entrypoint requiring compiled output.
examples/typescript-non-erasable/build.js Adds build harness entry for the new example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/typescript/TypeScriptPlugin.js Outdated
Comment thread lib/config/defaults.js
Comment thread examples/typescript/webpack.config.js
Comment thread examples/typescript/template.md
Comment thread examples/typescript-non-erasable/template.md
Comment thread test/configCases/typescript/type-only-imports/test.filter.js
Comment thread test/configCases/typescript/loader-source-map/test.filter.js
Comment thread examples/typescript/test.filter.js
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

This PR is packaged and the instant preview is available (1d8fdc1).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@1d8fdc1
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@1d8fdc1
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@1d8fdc1

@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 96.05263% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.92%. Comparing base (5d48595) to head (410928f).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
lib/typescript/TypeScriptPlugin.js 95.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #20964      +/-   ##
==========================================
+ Coverage   90.90%   90.92%   +0.02%     
==========================================
  Files         571      572       +1     
  Lines       58301    58375      +74     
  Branches    15659    15682      +23     
==========================================
+ Hits        53000    53080      +80     
+ Misses       5301     5295       -6     
Flag Coverage Δ
integration 89.65% <96.05%> (+0.02%) ⬆️
test262 45.43% <62.50%> (+0.03%) ⬆️
unit 36.44% <81.25%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 113 out of 116 changed files in this pull request and generated 34 comments.

Comment thread lib/typescript/TypeScriptPlugin.js Outdated
Comment on lines +79 to +107
if (!("stripTypeScriptTypes" in mod)) {
throw new ModuleBuildError(
new Error(
"experiments.typescript requires Node.js >= 22.7. " +
"`module.stripTypeScriptTypes` is not available on this Node.js version."
)
);
}

const [rawSource, inputSourceMap, ...rest] = result;
const needSourceMap =
module.useSourceMap || module.useSimpleSourceMap;
const inputSource = removeBOM(
Buffer.isBuffer(rawSource) ? rawSource.toString("utf8") : rawSource
);
const inputSourceString =
typeof inputSource === "string"
? inputSource
: inputSource.toString("utf8");

let strippedSource;
try {
strippedSource =
// eslint-disable-next-line n/no-unsupported-features/node-builtins
mod.stripTypeScriptTypes(inputSourceString, {
mode: "transform",
sourceMap: needSourceMap,
sourceUrl: module.resource
});
Comment on lines +8 to +13
"verbatimModuleSyntax": true,
"paths": {
"@components/*": ["components/*"],
}
}
}
Comment on lines +3 to +7
// Node.js's `module.stripTypeScriptTypes` was added in v22.6 (strip-only) and
// gained `mode: "transform"` (needed for enums/namespaces) in v22.7. Guard
// against versions that lack the API entirely.

module.exports = () => "stripTypeScriptTypes" in require("module");
Comment on lines +8 to +13
"verbatimModuleSyntax": true,
"paths": {
"@components/*": ["components/*"],
}
}
}
Comment on lines +3 to +7
// Node.js's `module.stripTypeScriptTypes` was added in v22.6 (strip-only) and
// gained `mode: "transform"` (needed for enums/namespaces) in v22.7. Guard
// against versions that lack the API entirely.

module.exports = () => "stripTypeScriptTypes" in require("module");
const supportsOptionalChaining = require("../../test/helpers/supportsOptionalChaining");

module.exports = () => supportsOptionalChaining();
module.exports = () => "stripTypeScriptTypes" in require("module");
Comment on lines +11 to +14
Only the **erasable** TypeScript subset is supported here. For
non-erasable syntax (enums, namespaces, parameter-property constructors,
JSX/`.tsx`), use `ts-loader` or `swc-loader` — see the
`typescript-non-erasable` example.
Comment on lines +7 to +21
// >= 22.7) to strip type annotations from `.ts`, `.cts`, `.mts` files at
// build time. It also registers the matching module rules, adds `.ts` to
// `resolve.extensions`, sets up `extensionAlias` so `.js`/`.cjs`/`.mjs`
// imports also try their `.ts`/`.cts`/`.mts` siblings, and enables
// tsconfig.json resolution.
//
// `stripTypeScriptTypes` is purely a transpile step — it strips type
// annotations and does NOT type-check. We pair it with
// `fork-ts-checker-webpack-plugin`, which runs `tsc --noEmit` in a worker
// so build errors and type errors both surface at compile time, the same
// trade-off as `ts-loader { transpileOnly: true }` + `ForkTsChecker`.
//
// Limitations: only the **erasable** TypeScript subset is supported —
// `enum`, `namespace`, parameter-property constructors, decorator
// metadata, and JSX/`.tsx` are NOT handled here. For those, use
Comment on lines +1 to +5
// This example uses syntax that Node.js's built-in `stripTypeScriptTypes`
// rejects (enums, parameter-property constructors, namespaces) — i.e. it
// goes beyond the "erasable" subset enforced by tsconfig's
// `erasableSyntaxOnly`. Projects that rely on these features still need a
// real TypeScript transpiler such as `ts-loader` or `swc-loader`.
Comment on lines +5 to +11
That transform uses Node.js's `module.stripTypeScriptTypes` and therefore
only handles the **erasable** TypeScript subset — types, `import type`,
`as`-casts, generics, etc. It rejects syntax that emits runtime code:
`enum`, `namespace`, parameter-property constructors, `export =`, decorator
metadata, JSX/`.tsx`.

If your project uses any of that non-erasable syntax, keep using a real
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 15, 2026

Merging this PR will improve performance by 20.19%

⚡ 13 improved benchmarks
❌ 9 regressed benchmarks
✅ 122 untouched benchmarks
⏩ 72 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-development","mode":"development"}' 1,044 KB 834.1 KB +25.17%
Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-development","mode":"development"}' 816.7 KB 1,132.2 KB -27.86%
Memory benchmark "wasm-modules-sync", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,228.3 KB 395.5 KB ×3.1
Memory benchmark "asset-modules-inline", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 232.6 KB 922.8 KB -74.79%
Memory benchmark "future-defaults", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 325.4 KB 144.4 KB ×2.3
Memory benchmark "react", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 207.1 KB 133.6 KB +55.01%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 248 KB 353 KB -29.74%
Memory benchmark "asset-modules-resource", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 362.7 KB 209.6 KB +73.05%
Memory benchmark "devtool-eval", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 407.3 KB 917 KB -55.59%
Memory benchmark "many-modules-commonjs", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 666.3 KB 198.2 KB ×3.4
Memory benchmark "css-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 662.4 KB 414 KB +59.98%
Memory benchmark "concatenate-modules", scenario '{"name":"mode-development","mode":"development"}' 784.8 KB 994.3 KB -21.07%
Memory benchmark "concatenate-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 132 KB 435.7 KB -69.7%
Memory benchmark "css-modules", scenario '{"name":"mode-production","mode":"production"}' 11 MB 9.1 MB +21.26%
Memory benchmark "cache-filesystem", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 842 KB 189.2 KB ×4.5
Memory benchmark "many-chunks-esm", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 248.7 KB 170.3 KB +46.03%
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-development","mode":"development"}' 4.9 MB 3.9 MB +24.27%
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 3,716.3 KB 168.1 KB ×22
Memory benchmark "many-modules-esm", scenario '{"name":"mode-production","mode":"production"}' 8.7 MB 7 MB +23.3%
Memory benchmark "many-modules-commonjs", scenario '{"name":"mode-production","mode":"production"}' 6.8 MB 8.6 MB -20.98%
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/review-typescript-support-Iq6Sj (410928f) with main (5d48595)

Open in CodSpeed

Footnotes

  1. 72 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 114 out of 118 changed files in this pull request and generated 6 comments.

Comment thread lib/typescript/TypeScriptPlugin.js Outdated
Comment on lines +79 to +86
if (!("stripTypeScriptTypes" in mod)) {
throw new ModuleBuildError(
new Error(
"experiments.typescript requires Node.js >= 22.7. " +
"`module.stripTypeScriptTypes` is not available on this Node.js version."
)
);
}
Comment on lines +19 to +23
// Limitations: only the **erasable** TypeScript subset is supported —
// `enum`, `namespace`, parameter-property constructors, decorator
// metadata, and JSX/`.tsx` are NOT handled here. For those, use
// `ts-loader` or `swc-loader` (see the `typescript-non-erasable`
// example).
Comment on lines +11 to +14
Only the **erasable** TypeScript subset is supported here. For
non-erasable syntax (enums, namespaces, parameter-property constructors,
JSX/`.tsx`), use `ts-loader` or `swc-loader` — see the
`typescript-non-erasable` example.
Comment on lines +5 to +11
That transform uses Node.js's `module.stripTypeScriptTypes` and therefore
only handles the **erasable** TypeScript subset — types, `import type`,
`as`-casts, generics, etc. It rejects syntax that emits runtime code:
`enum`, `namespace`, parameter-property constructors, `export =`, decorator
metadata, JSX/`.tsx`.

If your project uses any of that non-erasable syntax, keep using a real
Comment on lines +3 to +7
// Node.js's `module.stripTypeScriptTypes` was added in v22.6 (strip-only) and
// gained `mode: "transform"` (needed for enums/namespaces) in v22.7. Guard
// against versions that lack the API entirely.

module.exports = () => "stripTypeScriptTypes" in require("module");
const supportsOptionalChaining = require("../../test/helpers/supportsOptionalChaining");

module.exports = () => supportsOptionalChaining();
module.exports = () => "stripTypeScriptTypes" in require("module");
@alexander-akait alexander-akait force-pushed the claude/review-typescript-support-Iq6Sj branch from baec0fe to 1030d25 Compare May 16, 2026 09:43
alexander-akait and others added 7 commits May 18, 2026 15:16
Adds `experiments.typescript` (and `module.parser.javascript*.typescript`)
opt-in that uses Node.js's built-in `module.stripTypeScriptTypes`
(Node.js >= 22.7) to transform `.ts`, `.cts`, `.mts`, and
`data:(text|application)/typescript` modules at build time.

Pipeline:

* `lib/typescript/TypeScriptPlugin.js` — registered from
  `WebpackOptionsApply` when `experiments.typescript` is on. Taps
  `NormalModule.getCompilationHooks(compilation).processResult`, runs
  `stripTypeScriptTypes` in `mode: "transform"`, and composes the
  resulting source map with any upstream loader source map via
  `webpack-sources.SourceMapSource` so a loader emitting TS (e.g. a
  preprocessor) keeps its provenance through to the bundle.
* TS syntax errors and a missing Node API surface as
  `ModuleBuildError` attributed to the offending module, not uncaught
  exceptions. `.tsx`/JSX is rejected with a friendly "use a TSX-capable
  loader" message; BOM is stripped before the transform; malformed
  inline source maps degrade to "no map" instead of crashing.

Defaults:

* `lib/config/defaults.js` adds module rules for `.ts`/`.cts`/`.mts`
  and the `text/typescript`/`application/typescript` MIME types,
  resolves `.ts` ahead of `.js`, sets `extensionAlias` (`.js`→[`.js`,
  `.ts`], `.cjs`→[`.cjs`, `.cts`], `.mjs`→[`.mjs`, `.mts`]), turns on
  `resolve.tsconfig`, and wires the parser flag. Auto-enabled when
  `experiments.futureDefaults` is true.

Schema & types:

* `schemas/WebpackOptions.json`, `declarations/WebpackOptions.d.ts`,
  and `types.d.ts` get `experiments.typescript`,
  `experimentsNormalized.typescript`, and
  `JavascriptParserOptions.typescript`, all flagged `@experimental`.

Tests (`test/configCases/typescript/*`):

* Coverage for `.ts`/`.cts`/`.mts` resolution, `require`, dynamic
  import, namespaces/enums, data URIs, type-only imports,
  side-effects-and-only-types, conflict resolution between `.js` and
  `.ts` siblings, directory-index resolution, `package.json#imports`
  field, query/hash on requests, provided-exports.
* `loader-source-map` verifies `SourceMapSource` composition through
  a banner loader on a `.ts` module.
* `experiments-and-ts-loader` confirms `experiments.typescript: true`
  coexists with `ts-loader { transpileOnly: true }` — the loader runs
  first, the plugin sees plain JS, `stripTypeScriptTypes` is a no-op,
  bundle still works.

Examples:

* `examples/typescript/` — the recommended setup: just
  `experiments.typescript: true` plus
  `fork-ts-checker-webpack-plugin` for the type-check half (matching
  ts-loader transpileOnly + ForkTsChecker workflow).
* `examples/typescript-non-erasable/` — the previous ts-loader +
  fork-ts-checker example, renamed and reworded to motivate when to
  reach for it: enums, namespaces, parameter-property constructors,
  decorator metadata, JSX/.tsx — anything beyond TypeScript's
  erasable subset.

Co-authored-by: Tobias Koppers <[email protected]>
- Wrap `keyof AllCodeGenerationSchemas` in parens so `jsdoc/type-formatting`
  accepts the conditional `@typedef` for `CodeGenValue`.
- Add a `@template {string} K` to the `CodeGenMapOverloads` typedef so
  `jsdoc/require-template` no longer flags the inline `<K extends string>`
  generic parameters on each `@property`.

The two errors reproduce on a clean `origin/main` checkout (introduced by
the JSDoc rule tightening in 20906/20926) and were the only remaining
`yarn lint:code` failures on this branch.
- Mark `parser.javascript.typescript` as `"experimental": true` in
  the schema so it matches the experiments-level flag and so the
  generated `@experimental` JSDoc tag is emitted on
  `JavascriptParserOptions.typescript` in declarations.
- Regenerate `types.d.ts`, `declarations/WebpackOptions.d.ts`, and
  `schemas/WebpackOptions.check.js` against `[email protected]`.
- Revert the `@template {string} K` workaround on
  `CodeGenMapOverloads` — the updated `eslint-plugin-jsdoc` no
  longer trips on inline per-property generics, so the workaround
  was unneeded and removing it lets `generate-types` re-emit the
  `CodeGenMapOverloads`/`CodeGenValue`/`AllCodeGenerationSchemas`
  interfaces that the workaround had silently stripped.
Node 26 stabilised `module.stripTypeScriptTypes` and dropped both the
legacy `mode: "strip-only"` value AND `mode: "transform"`. Switch the
plugin to `mode: "strip"` — the only value Node 26 accepts and one
that's also valid on Node 22.6+. The trade-off is that strip mode
rejects non-erasable syntax (`enum`, `namespace`, parameter-property
constructors), which aligns with what `experiments.typescript` was
always documented to support; for non-erasable code use the
`examples/typescript-non-erasable` (ts-loader) path.

* Node 26 also rejects `sourceMap: true` in strip mode. Drop the
  option and construct a line-granularity identity source map
  manually — strip mode preserves the original line layout
  (annotations are replaced with whitespace) so an identity mapping
  is correct. The map is still composed with any upstream loader
  source map via `SourceMapSource`.
* Drop the now-unused `getExtractSourceMap` / `getDataURL` helpers
  from the plugin.
* `basic/index.ts` no longer uses `enum`; switched to a union-type
  alias.
* `namespace/index.ts` is now a negative test: the build is expected
  to fail with `TypeScript namespace declaration is not supported in
  strip-only mode`, asserted via a new `errors.js` fixture.

Add the `"typescript"` conditional-exports key to webpack's resolve
`conditionNames` when `experiments.typescript` is on (Node.js amaro
convention). Monorepo packages can now ship `.ts` sources alongside
compiled output:

```json
{
  "exports": {
    ".": {
      "typescript": "./src/index.ts",
      "import": "./dist/index.js"
    }
  }
}
```

New `test/configCases/typescript/conditional-exports/` covers this —
a `my-lib` fixture with both branches resolves to its `.ts` source.

Also addresses two follow-ups:

* `tsc -p tsconfig.types.test.json` failed on the
  `loader-source-map/loader/banner-loader.js` fixture — type the
  loader via `LoaderDefinition` and add the required `file` property
  on the emitted source map literal.
* Add `AACA` and `amaro` to the cspell wordlist.
Restructure `TypeScriptPlugin` for readability:

* Pull the per-step logic out of the `processResult` tap into small,
  focused module-level helpers:
  - `isTypeScriptResource(resource)` — single regex gate.
  - `toBomFreeString(source)` — Buffer/string coercion + BOM strip.
  - `stripTypes(input)` — wraps `mod.stripTypeScriptTypes` in a
    `ModuleBuildError`.
  - `createIdentitySourceMap(resource, source)` — line-granularity
    identity map for the strip-types output.
  - `composeWithLoaderSourceMap(...)` — `SourceMapSource` chaining.
* Move the tap body into `_processResult(result, module)` typed
  against `NormalModule`'s `Result` tuple so JSDoc types stay tight.
* Lift the inline error strings to module-level constants.

Drop the `sourceUrl: module.resource` option. Node 26 still accepts
it but emits a `//# sourceURL=…` legacy V8 pragma into the JS output,
which adds noise; webpack's source map handling replaces what that
pragma was for. Pass only `mode: "strip"`.

Behavioural diff: none. 49/49 typescript configCases still pass on
Node 22.22.3 and Node 26.1.0; lint/tsc/types-test/fmt/spellcheck all
clean.
Folds the TS-related defaults additions back into the inline
snapshots that picked up main's `experiments.html` / `sourceImport`
defaults during the rebase. No source code change.
`test/configCases/typescript/namespace/` was a negative test asserting
the build fails with `TypeScript namespace declaration is not
supported in strip-only mode`. It passes under `ConfigTestCases` but
fails under `ConfigCacheTestCases` (yarn test:basic) because webpack's
persistent cache logs `Pack got invalid because of write to: …` on the
second cache-validation run — the errored module doesn't serialize
cleanly and triggers cache invalidation, which the test harness
treats as an `InfrastructureLogs` failure when `run >= 2`.

The namespace limitation is already documented in two places: the
changeset (`.changeset/experimental-typescript-support.md`) and the
`examples/typescript-non-erasable/` example which motivates using
`ts-loader` for namespaces/enums/parameter-property constructors.
The error itself comes from Node.js and is stable. Drop the fragile
test rather than carry a cache-special-case workaround.

ConfigTestCases (47/47) and ConfigCacheTestCases (77/77 typescript
variants) now both pass on Node.js 22.22.3 and 26.1.0.
@alexander-akait alexander-akait force-pushed the claude/review-typescript-support-Iq6Sj branch from 4aa7711 to 410928f Compare May 18, 2026 15:18
@github-actions
Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/review-typescript-support-Iq6Sj into main will be
98.95%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.77%100%100%98.77%91
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js91.30%100%100%91.30%140, 142–143, 145, 147, 153–154, 161, 168, 90
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/typescript
   test.filter.js100%100%100%100%
examples/typescript-non-erasable
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%37
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js98.72%100%100%98.72%206, 226, 382
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.55%100%100%98.55%1554, 1850, 1857, 1865, 1887, 2783, 3208, 3870, 3899, 3952–3953, 3957, 3962, 3978–3979, 3993–3994, 3999–4000, 4477, 4503, 493, 498, 5211, 5292, 5307, 5332–5333, 5335, 5659, 5664, 5670, 5673, 5685, 5687, 5691, 5707, 5722, 5754, 5808, 5832, 5946, 712–713
   Compiler.js99.55%100%100%99.55%1116–1117, 1125
   ConcatenationScope.js98.59%100%100%98.59%189
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js100%100%100%100%
   ContextModuleFactory.js97.75%100%100%97.75%258, 393, 418, 443, 447, 458
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js98.92%100%100%98.92%158–159, 175, 194, 268
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.20%100%100%98.20%379, 425
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js97.88%100%100%97.88%237, 378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.96%100%100%98.96%424–428, 576
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.50%100%100%99.50%182, 2252–2253, 2256, 2267, 2278, 2289, 278, 3694, 3709, 3733
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.74%100%100%98.74%399, 401, 405
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1304, 1309, 1370, 1384, 1446, 1455
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1004
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.69%100%100%99.69%645
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.78%100%100%97.78%1020, 1036, 1123, 1774, 1779–1789, 708, 711, 728, 745, 986
   NormalModuleFactory.js99.47%100%100%99.47%1074, 1383, 473, 485
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js98.85%100%100%98.85%519–520, 525, 527, 591
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js99.16%100%100%99.16%267–268, 610
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js98.86%100%100%98.86%134–135
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%325
   cli.js98.71%100%100%98.71%117, 469, 501, 543, 813
   index.js100%100%100%100%
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.22%100%100%97.22%196, 218, 220
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModulesPlugin.js97.77%100%100%97.77%285, 309, 312, 364, 40
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%71, 83, 91
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%106, 113–114, 122, 89
   PackFileCacheStrategy.js96.40%100%100%96.40%1250, 1350, 1354, 1416, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 722, 728, 762, 768, 774, 779, 790, 799, 804–805, 807, 824, 830–831, 833
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   browserslistTargetHandler.js100%100%100%100%
   defaults.js99.18%100%100%99.18%1401–1403, 1411, 271, 274, 279, 283, 475
   normalization.js99%100%100%99%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   ContainerEntryDependency.js100%100%100%100%
   ContainerEntryModule.js100%100%100%100%
   ContainerEntryModuleFactory.js100%100%100%100%
   ContainerExposedDependency.js100%100%100%100%
   ContainerPlugin.js100%100%100%100%
   ContainerReferencePlugin.js100%100%100%100%
   FallbackDependency.js100%100%100%100%
   FallbackItemDependency.js100%100%100%100%
   FallbackModule.js100%100%100%100%
   FallbackModuleFactory.js100%100%100%100%
   HoistContainerReferencesPlugin.js100%100%100%100%
   ModuleFederationPlugin.js100%100%100%100%
   RemoteModule.js100%100%100%100%
   

@alexander-akait alexander-akait merged commit 1d8fdc1 into main May 18, 2026
61 checks passed
@alexander-akait alexander-akait deleted the claude/review-typescript-support-Iq6Sj branch May 18, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce typescript/auto Mode for Faster TypeScript Transpilation in Webpack

2 participants