feat: support tsx as a fallback loader for TypeScript and JSX configs#4796
Conversation
Register `tsx/cjs` manually when the loaders known to `interpret` are unavailable. It cannot go through `interpret`/`rechoir`: the `tsx/cjs` entry point exists only in the package's `exports` map, which `rechoir`'s resolver (legacy `resolve@1.x`) does not support, so it is resolved with `createRequire` from the configuration file's location and required directly, installing its require hook. The loaders listed in `interpret` keep priority; `tsx` is tried only after all of them have failed. For `.mts`, which has no `interpret` entry at all, `tsx` is registered best-effort and the original error reporting is preserved when it is missing. When nothing works, the `tsx` resolution failure is appended to the existing loader failure list before "Please install one of them". The fixture shadows the `interpret` loaders with generated throwing stubs (kept out of the repository — `test/**/node_modules` is already ignored) so the tests exercise the real `tsx` package from the root dev dependencies for both `.ts` and `.mts` configurations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hv8piVkEGYdWzWoWCtMcif
`tsx/cjs` also installs a require hook for `.jsx` (esbuild transform, including `@jsx` pragma support), so offer it as a fallback there too when the `interpret` loaders (`@babel/register`, `sucrase`) are not installed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hv8piVkEGYdWzWoWCtMcif
🦋 Changeset detectedLatest commit: 58a215d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
One or more co-authors of this pull request were not found. You must specify co-authors in commit message trailer via: Supported
Alternatively, if the co-author should not be included, remove the Please update your commit message(s) by doing |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4796 +/- ##
==========================================
- Coverage 93.43% 93.30% -0.14%
==========================================
Files 14 14
Lines 5420 5463 +43
Branches 788 792 +4
==========================================
+ Hits 5064 5097 +33
- Misses 354 364 +10
Partials 2 2
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a tsx-based fallback loader path for TypeScript/JSX webpack config files when interpret/rechoir can’t register any of their known loaders, and introduces a fixture that validates the fallback against the real tsx package.
Changes:
- Add
tsx/cjsregistration (viacreateRequire(configFilePath)) as a fallback whenrechoir.prepare()fails for TS/JSX config extensions, and as best-effort support for.mts. - Add a new
test/build/config-format/typescript-tsx/fixture + Jest suite that disablesinterpretloaders via throwing stubs to force exercising thetsxfallback. - Add
tsxas a root devDependency and include a changeset entry for the feature.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
packages/webpack-cli/src/webpack-cli.ts |
Implements the tsx fallback loader registration and integrates it into the config loading flow. |
test/build/config-format/typescript-tsx/typescript.test.mjs |
New integration tests forcing the tsx fallback by shadowing interpret loaders with throwing stubs. |
test/build/config-format/typescript-tsx/webpack.config.ts |
TS config fixture consumed by the new test. |
test/build/config-format/typescript-tsx/webpack.config.mts |
MTS config fixture consumed by the new test. |
test/build/config-format/typescript-tsx/webpack.config.jsx |
JSX config fixture consumed by the new test. |
test/build/config-format/typescript-tsx/main.ts |
Minimal entry file for the build output assertions. |
test/build/config-format/typescript-tsx/package.json |
Fixture-local package settings (CommonJS package type + engine constraint). |
package.json |
Adds tsx devDependency for running the new fixture against the real package. |
package-lock.json |
Locks tsx (and its deps) into the dependency graph. |
eslint.config.mjs |
Ignores the JSX fixture config file for ESLint parsing. |
.changeset/tsx-config-loader.md |
Declares a minor bump for webpack-cli describing the new fallback behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch (err) { | ||
| return err as Error; | ||
| } |
| // TypeScript and JSX configuration files that `tsx` can load through its | ||
| // CommonJS require hook (`tsx/cjs`). Used as a fallback when the loaders | ||
| // listed in `interpret` are unavailable (for `.mts` there is no `interpret` | ||
| // entry at all, so `tsx` is the only fallback). | ||
| const TSX_LOADABLE_EXTENSIONS = new Set([".ts", ".tsx", ".cts", ".mts", ".jsx"]); |
- Normalize non-Error throws in the tsx registration so the
error-reporting path can rely on `.message`.
- Fix the pre-existing "Unable load" typo ("Unable to load").
- Cover `.cts` (special-cased extension mapping) and `.tsx` (types and
JSX together) in the fixture, alongside `.ts`, `.mts` and `.jsx`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv8piVkEGYdWzWoWCtMcif
Summary
Adds
tsxas a fallback loader for TypeScript and JSX configuration files (.ts,.tsx,.cts,.mts,.jsx), used when none of the loaders known tointerpret(ts-node,sucrase,@babel/register,esbuild-register,@swc/register) are installed.Closes #4790
The approach in #4790 — appending
"tsx/cjs"tointerpret's loader tables — cannot work:rechoirresolves loader modules with the legacyresolve@1.xpackage, which does not support packageexportsmaps — andtsx/cjsexists only in tsx'sexportsmap (there is no physicalcjs.jsfile).rechoir.prepare()therefore always fails to resolve the real package (Cannot find module 'tsx/cjs'); the tests in #4790 only passed because they committed a hand-written faketsxstub into a fixturenode_modules.Implementation
interpret/rechoirflow is untouched and its loaders keep priority.rechoir.prepare()fails for a config with a tsx-loadable extension, the CLI registers tsx itself viacreateRequire(configFilePath)("tsx/cjs")— Node's own resolver, which understandsexportsmaps and resolves from the user's project. Requiringtsx/cjsinstalls its require hook, then config loading proceeds as usual..mts, which has nointerpretentry at all, gets a best-effort tsx registration; when tsx is missing, the error output is identical tomaintoday (the originalimport()failure is reported).Please install one of them(first line only, to avoid the noisy require stack).The tsx registration has no dependency on
interpret/rechoir, so it survives their planned future removal unchanged.Test plan
test/build/config-format/typescript-tsx/exercises the realtsxpackage (added as a root devDependency) for.ts,.mtsand.jsxconfigs. Because the repository has workinginterpretloaders installed, the test'sbeforeAllgenerates throwing stubs for all of them into the fixture'snode_modules(already gitignored — nothing fake is committed), so the assertions can only pass through tsx..tsexits 2 listing all loader failures including tsx;.mtsreports the original import error exactly as before.config-formatsuites (37 tests) and all 69config/extendssuites (152 tests) pass;tsc --build, ESLint, Prettier and cspell are clean.webpack-cli: minor).🤖 Generated with Claude Code
https://claude.ai/code/session_01Hv8piVkEGYdWzWoWCtMcif