…onfigs
A `.ts` config is transpiled to a temp `.mjs` and its import tree is transpiled
with it, but two things were missed, and both surface only at runtime.
Dynamic `import('./module')` was invisible to the transpiler: the dependency
scan and the rewrite pass matched `from '...'` and `require('...')` only, so a
lazily imported module was never emitted and the specifier still pointed at a
`.ts` path — ERR_MODULE_NOT_FOUND. Static and dynamic specifiers now share one
resolver, so both follow the same ESM resolution.
The CommonJS shim was gated on `require(` and `module.exports`, so the standard
`if (require.main === module)` entrypoint idiom got no shim and the transpiled
file threw "require is not defined in ES module scope". Detection now counts
bare `require` / `module` identifiers, ignores quoted occurrences such as
`from 'module'`, and skips files that declare their own binding — which also
stops the shim redeclaring a user's own `const require = createRequire(...)`.
Unit suite: 769 -> 771 passing, 0 failing.
Motivation/Description of the PR
A .ts config is not executed as TypeScript — lib/config.js transpiles it to a temp .mjs and transpileTypeScript walks and transpiles its import tree along with it. Two gaps in that walk make perfectly ordinary TypeScript fail, and both only surface at runtime, with errors that point away from the real cause.
1. Dynamic import() is invisible to the transpiler. The dependency scan and the rewrite pass matched from '...' and require('...') only. A module reached through await import('./module') was therefore never emitted, and the specifier survived transpilation still pointing at an extensionless .ts path:
The same import written statically at the top of the config works, which makes the failure look like an ESM-vs-tsx resolution problem in the runner rather than a gap in the transpiler. In our suite this cost a real workaround: both lifecycle hooks shelled out to execSync('npx tsx <script>') for a year, with a code comment blaming the worker ESM context.
Static and dynamic specifiers now share one resolver, so both follow identical ESM resolution — path aliases, index.ts, .js-that-is-really-.ts, and the extension-append fallback included.
2. A bare require / module identifier gets no CommonJS shim. Shim injection was gated on /\brequire\s*\(/ and /\b(module\.exports|exports\.)/, so the standard entrypoint idiom matched neither:
Detection now counts bare require / module identifiers. Two refinements keep it from over-firing:
Verification
New fixture test/data/typescript-config-dynamic-import/ covers both paths in one realistic shape: a config that dynamically imports a lifecycle module, which in turn statically imports a third file and carries a require.main === module guard. Two unit tests assert the module tree is fully emitted and reachable, that the guarded file imports without throwing, and that the guard stays dormant on import.
Unit suite before this change: 769 passing, 11 pending, 0 failing. After: 771 passing, 11 pending, 0 failing. eslint clean on the touched files.
Also checked against the real-world case that prompted this, a 10-file config import tree in a private suite:
Applicable helpers:
Applicable plugins:
Type of change
Checklist: