You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(test): transpile runtime .ts imports with oxc-node instead of tsx (#5965)
## Motivation
The vitest run loads many `.ts` files at runtime via Node's native
`import()` — the egg loader resolving fixtures / plugins / app code,
plus the workspace `src` exports that resolve under `node_modules` (e.g.
`node_modules/egg/src/index.ts`). That path is driven by the `--import`
hook in `test.env.NODE_OPTIONS`, which was `tsx/esm`.
Swap it to `@oxc-node/core/register` (oxc / Rust-based): noticeably
faster than tsx, and it transpiles workspace `src` + decorators
correctly. `@oxc-node/core` >= `0.1.0` fixes the earlier `Cannot read
properties of undefined (reading 'mode')` crash that had blocked this
(the stale FIXME in the config).
## Changes
- `vitest.config.ts`: `NODE_OPTIONS` hook `tsx/esm` →
`@oxc-node/core/register`; replace the stale FIXME comment.
- `pnpm-workspace.yaml`: catalog `@oxc-node/core` `^0.0.35` → `^0.1.0`.
- `package.json`: add `@oxc-node/core: catalog:` to root
devDependencies.
`tsx` is intentionally **kept** as a dependency — it is still used by
the `worker_threads` tests, egg-bundler, and a couple of other spots.
## Test evidence (local, Node 22, `isolate:false`, `--retry 2`)
Full suite, oxc-node vs tsx — identical pass/fail (only the
env-dependent `orm-plugin`/`ssrf` failures), **zero transpile errors**
across all decorator-heavy tegg / cluster / mock packages:
| | tsx | oxc-node |
|---|---|---|
| wall | 84s | **79s** |
| `tests` (cumulative; where the runtime hook applies) | 817s | **764s**
(~6.5% faster) |
A transpile-heavy subset showed up to ~13%. The win is on the
runtime-transpilation portion; overall suite time is dominated by egg
app boot + real I/O, so the wall-clock delta is modest but consistent.
## Note
Based on `worktree-vitest-isolate-false` (the isolate:false fix branch /
#5964) so CI runs on a green base and the diff is only this change. Will
retarget to `next` once that lands.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Updated the runtime TypeScript loader used for dynamic `.ts` imports
during tests to the newer register-based approach, reducing crashes.
* Improved Vitest child-process configuration to avoid enabling
conflicting TypeScript loader hooks at the same time.
* Prevented duplicate work during concurrent dynamic imports by sharing
in-flight module loading.
* **Documentation**
* Refreshed and expanded the Vitest isolation leak troubleshooting guide
with an added concurrency race explanation and fix notes.
* Updated wording in the existing isolate:false diagnosis notes and
added a new changelog entry describing the fix.
* **Chores**
* Updated the development tooling version for the TypeScript runtime
register package in the workspace catalog.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: killa <killa@killadeMacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- note: `tegg/plugin/tegg/test/MultiAppParallel.test.ts` ("…under concurrent boot") flaked ~12% (tsx) / ~24% (oxc-node) on macOS CI with `Can not find plugin watcher` or `Cannot convert undefined or null to object`. NOT caused by the tsx→oxc-node switch (both transpilers flake). Root cause: under `describe.concurrent`, multiple app loaders call `importModule()` on the same `.ts` module simultaneously; the transpile loaders recompile per-`import()` (tsx appends `?<ts>`, defeating Node's dedup) so a concurrent first-load can return a namespace whose `default` is `undefined` → empty framework `config/plugin` (watcher loses its `path`) or `Object.getOwnPropertyNames(undefined)` in `loadExtend`. Fix: `importModule` shares one in-flight `import()` per URL. 40/40 green under both transpilers after; full suite stays 527 files / 3430 tests, 0 failures. Heisenbug (instrumentation masks it); the `.egg/manifest.json` read/write race was a red herring. Recorded as root cause #5 on the concept page.
10
+
5
11
## [2026-06-27] workflow | CI surfaces single-run parallelism metrics for the isolate:false suite
- note: Under root `pool:threads` + `isolate:false`, two realm-global leaks caused nondeterministic cross-file/cross-project failures. (1) `setSnapshotModuleLoader` left module-level `_snapshotModuleLoader`/`isESM=false` set (no-op test teardown), poisoning module resolution for later files (`Can not find plugin …`). (2) `mock.mockContext()` reused `currentContext` from a different app, binding helpers to the wrong app config (surl/csrf failures). Fixed both at the source. Full Node-22 suite: 15 → 3 failing files (remaining 2 environmental MySQL/DNS; `multipart/file-mode` is a pre-existing load flake that also fails under `isolate:true`). Reproduce on Node 22/24 with a utoo install — not Node 26 / bare pnpm.
35
+
- note: Under root `pool:threads` + `isolate:false`, two realm-global leaks caused nondeterministic cross-file/cross-project failures. (1) `setSnapshotModuleLoader` left module-level `_snapshotModuleLoader`/`isESM=false` set (no-op test teardown), poisoning module resolution for later files (`Can not find plugin …`). (2) `mockContext()` reused `currentContext` from a different app, binding helpers to the wrong app config (surl/csrf failures). Fixed both at the source. Full Node-22 suite: 15 → 3 failing files (remaining 2 environmental MySQL/DNS; `multipart/file-mode` is a pre-existing load flake that also fails under `isolate:true`). Reproduce on Node 22/24 with a utoo install — not Node 26 / bare pnpm.
0 commit comments