Skip to content

Commit d46da08

Browse files
fengmk2claude
andcommitted
perf(vpx): use in-thread hooks on Node.js 22.22.3+ and 24.11.1+
The off-thread `module.register()` hooks roughly double script startup. Use `module.registerHooks()` wherever it has the two fixes the hooks need, nodejs/node#59011 (resolve conditions) and nodejs/node#59929 (a null source for CommonJS from a sync load hook), the releases tsx also switches at. Only 22.x before 22.22.3 and 24.11.0 keep `module.register()`. With the project-local bin, a two-module `.mts` script went from 78 to 57 ms on Node.js 22.23 and from 69 to 52 ms on 24.11. A Node.js 24.12 fixture covers the in-thread path on an LTS line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent d161300 commit d46da08

16 files changed

Lines changed: 152 additions & 27 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Counter {
2+
count = 1;
3+
}
4+
5+
export function describe(): string {
6+
return `cts counter=${new Counter().count}`;
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// The in-thread hooks report the same error for a `.cts` with ES module syntax.
2+
try {
3+
await import('./both.cts');
4+
} catch (error) {
5+
console.log((error as Error).message);
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import nested from './nested.cts';
2+
3+
console.log(nested.name);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Named {
2+
name = 'commonjs .cts';
3+
}
4+
module.exports = new Named();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Node.js 24.11.1+ runs the hooks in-thread through module.registerHooks().
2+
import { createRequire } from 'node:module';
3+
4+
enum Path {
5+
Sync = 'registerHooks',
6+
}
7+
8+
class Resource {
9+
disposed = false;
10+
[Symbol.dispose]() {
11+
this.disposed = true;
12+
}
13+
}
14+
15+
const resource = new Resource();
16+
{
17+
using held = resource;
18+
void held;
19+
}
20+
21+
const require = createRequire(import.meta.url);
22+
console.log(Path.Sync, require('./legacy.cts').name, resource.disposed);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// CommonJS that require()s another CommonJS file, imported from ESM.
2+
const legacy = require('./legacy.cts');
3+
4+
module.exports = { name: `nested ${legacy.name}` };
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "command-vpx-script-node24",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"engines": {
7+
"node": "24.12.0"
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[[case]]
2+
name = "vpx_script_node24"
3+
vp = ["local", "global"]
4+
comment = "From Node.js 24.11.1 (and 22.22.3) the loader uses in-thread module.registerHooks() instead of a module.register() hooks thread."
5+
steps = [
6+
{ argv = ["vpx", "./main.ts"] },
7+
{ argv = ["vpx", "./import-cjs.ts"], comment = "An imported CommonJS module can require() other files" },
8+
{ argv = ["vpx", "./import-both.ts"], comment = "A .cts with ES module syntax fails with the same error on the in-thread path" },
9+
]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# vpx_script_node24
2+
3+
From Node.js 24.11.1 (and 22.22.3) the loader uses in-thread module.registerHooks() instead of a module.register() hooks thread.
4+
5+
## `vpx ./main.ts`
6+
7+
```
8+
registerHooks commonjs .cts true
9+
```
10+
11+
## `vpx ./import-cjs.ts`
12+
13+
An imported CommonJS module can require() other files
14+
15+
```
16+
nested commonjs .cts
17+
```
18+
19+
## `vpx ./import-both.ts`
20+
21+
A .cts with ES module syntax fails with the same error on the in-thread path
22+
23+
```
24+
Failed to transform file://<workspace>/both.cts: a `.cts` file is CommonJS, but this one uses ES module syntax (`import`, `export` or `import.meta`), which oxc-node does not compile to CommonJS. Rename it to `.mts` to run it as an ES module, or write it as CommonJS with `import x = require("...")` and `export =`.
25+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# vpx_script_node24
2+
3+
From Node.js 24.11.1 (and 22.22.3) the loader uses in-thread module.registerHooks() instead of a module.register() hooks thread.
4+
5+
## `vpx ./main.ts`
6+
7+
```
8+
registerHooks commonjs .cts true
9+
```
10+
11+
## `vpx ./import-cjs.ts`
12+
13+
An imported CommonJS module can require() other files
14+
15+
```
16+
nested commonjs .cts
17+
```
18+
19+
## `vpx ./import-both.ts`
20+
21+
A .cts with ES module syntax fails with the same error on the in-thread path
22+
23+
```
24+
Failed to transform file://<workspace>/both.cts: a `.cts` file is CommonJS, but this one uses ES module syntax (`import`, `export` or `import.meta`), which oxc-node does not compile to CommonJS. Rename it to `.mts` to run it as an ES module, or write it as CommonJS with `import x = require("...")` and `export =`.
25+
```

0 commit comments

Comments
 (0)