@nubjs/nub-win32-arm64 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nub.exe +0 -0
- package/package.json +2 -2
- package/runtime/addons/nub-native.node +0 -0
- package/runtime/compile-cache-restore.mjs +21 -0
- package/runtime/preload.mjs +78 -294
- package/runtime/transform-core.mjs +213 -12
- package/runtime/version.mjs +1 -1
- package/runtime/worker-polyfill.mjs +76 -27
- package/runtime/polyfills.mjs +0 -136
package/bin/nub.exe
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubjs/nub-win32-arm64",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "Nub binary for win32-arm64",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"repository": "https://github.com/
|
|
6
|
+
"repository": "https://github.com/nub-js/nub",
|
|
7
7
|
"os": [
|
|
8
8
|
"win32"
|
|
9
9
|
],
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// R8 early step for the compat tier (--import preload.mjs). Restores
|
|
2
|
+
// NODE_COMPILE_CACHE into process.env as a SIDE EFFECT, and must be the FIRST
|
|
3
|
+
// import in preload.mjs so it runs before transform-core.mjs's module body — which
|
|
4
|
+
// reads `NODE_COMPILE_CACHE === "0"` as nub's transpile-cache disable signal.
|
|
5
|
+
//
|
|
6
|
+
// Why a separate module instead of a statement in preload.mjs: ESM `import`s are
|
|
7
|
+
// hoisted and their module bodies evaluate in source order BEFORE any statement in
|
|
8
|
+
// the importer. transform-core.mjs is imported in preload.mjs, so the only way to
|
|
9
|
+
// mutate process.env ahead of transform-core's evaluation is from a module imported
|
|
10
|
+
// earlier in source order. The fast tier (preload.cjs, CommonJS) has no such
|
|
11
|
+
// hoisting and calls common.restoreCompileCacheEnv() directly instead.
|
|
12
|
+
//
|
|
13
|
+
// spawn.rs strips NODE_COMPILE_CACHE from the child env (so Node's V8 compile cache
|
|
14
|
+
// never caches nub's preload chain) and stashes the original value in a PID-keyed
|
|
15
|
+
// sentinel file; here we read + delete it and put it back. Restoring it in JS does
|
|
16
|
+
// not re-enable Node's bootstrap compile cache. See preload-common.cjs
|
|
17
|
+
// (restoreCompileCacheEnv) for the full mechanism — this file just calls it.
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
|
|
20
|
+
const require_ = createRequire(import.meta.url);
|
|
21
|
+
require_("./preload-common.cjs").restoreCompileCacheEnv();
|
package/runtime/preload.mjs
CHANGED
|
@@ -1,317 +1,101 @@
|
|
|
1
|
-
// Nub
|
|
1
|
+
// Nub compat-tier preload — Node 18.19–22.14, injected via `--import` (ESM).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
3
|
+
// The FAST tier (Node 22.15+) is loaded separately, as a `--require` CommonJS
|
|
4
|
+
// preload (runtime/preload.cjs), so Node keeps its synchronous `Module.runMain`
|
|
5
|
+
// CJS entry path (top-level `executionAsyncId()===1`, sync exception origin,
|
|
6
|
+
// `require.main.id` `'.'`, `module.parent` `null`). The mere presence of an
|
|
7
|
+
// `--import` ESM preload forces eager ESM-loader init that routes even a CJS entry
|
|
8
|
+
// through the async ESM module-job (R1) — so the fast tier must NOT use `--import`.
|
|
9
|
+
//
|
|
10
|
+
// THIS file stays the compat path: on 18.19–22.14, `module.registerHooks` does not
|
|
11
|
+
// exist and `require(esm)` is unreliable, so hooks run async in a dedicated loader
|
|
12
|
+
// worker via `module.register`, and the parser is preloaded via dynamic `import()`
|
|
13
|
+
// while we can still `await`. That async machinery is exactly why the compat tier
|
|
14
|
+
// keeps `--import` — its top-level `await` is accepted here (an `--import` ESM
|
|
15
|
+
// module may be async), and the < 22.15 floor has no equivalent sync surface.
|
|
16
|
+
//
|
|
17
|
+
// Resolution + transpile primitives come from runtime/transform-core.mjs; the
|
|
18
|
+
// non-tier-specific wiring (watch IPC, the CJS require() shim, clobbered-polyfill
|
|
19
|
+
// preloading, the Temporal lazy global) is shared verbatim with the fast tier via
|
|
20
|
+
// runtime/preload-common.cjs, so the two tiers can never drift.
|
|
21
|
+
|
|
22
|
+
// MUST be first: restores NODE_COMPILE_CACHE into process.env (R8) before
|
|
23
|
+
// transform-core.mjs's body evaluates, since transform-core reads it as the
|
|
24
|
+
// transpile-cache disable signal. ESM imports evaluate in source order, so this
|
|
25
|
+
// side-effecting import has to precede the transform-core import. See
|
|
26
|
+
// compile-cache-restore.mjs.
|
|
27
|
+
import "./compile-cache-restore.mjs";
|
|
16
28
|
import module from "node:module";
|
|
17
|
-
import { readdirSync } from "node:fs";
|
|
18
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
29
|
import { createRequire } from "node:module";
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
TRANSPILE_EXTS, DATA_EXTS, TS_PARENT_EXTS,
|
|
23
|
-
extname, isNodeModules, getTsconfigForDir, getPackageType,
|
|
24
|
-
resolveSpec, resolveCjsPath, requireTargetIsEsm, loadTranspile, loadData,
|
|
25
|
-
maybeSweepCache, setWatchHooks, ensureParser,
|
|
26
|
-
} from "./transform-core.mjs";
|
|
30
|
+
import * as core from "./transform-core.mjs";
|
|
27
31
|
|
|
28
32
|
const __require = createRequire(import.meta.url);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
try { process.send({ "watch:require": batch }); } catch {}
|
|
48
|
-
}
|
|
49
|
-
function reportWatchDep(path) {
|
|
50
|
-
if (!WATCH_REPORTING || !path || watchReported.has(path)) return;
|
|
51
|
-
watchReported.add(path);
|
|
52
|
-
watchPending.push(path);
|
|
53
|
-
if (!watchFlushScheduled) {
|
|
54
|
-
watchFlushScheduled = true;
|
|
55
|
-
// A scheduled immediate is drained before the loop would exit, so even a
|
|
56
|
-
// script that finishes synchronously flushes its deps. (Don't unref: an
|
|
57
|
-
// unref'd immediate is skipped on a synchronous exit, dropping the report.)
|
|
58
|
-
setImmediate(flushWatchDeps);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Report a directory's `.env*` files (the natural watch targets). Scanned once
|
|
62
|
-
// per directory, lazily.
|
|
63
|
-
const watchEnvScannedDirs = new Set();
|
|
64
|
-
function reportEnvFilesIn(dir) {
|
|
65
|
-
if (!WATCH_REPORTING || watchEnvScannedDirs.has(dir)) return;
|
|
66
|
-
watchEnvScannedDirs.add(dir);
|
|
67
|
-
let entries;
|
|
68
|
-
try { entries = readdirSync(dir); } catch { return; }
|
|
69
|
-
for (const name of entries) {
|
|
70
|
-
if (name === ".env" || name.startsWith(".env.")) reportWatchDep(join(dir, name));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
setWatchHooks({ reportDep: reportWatchDep, reportEnvDir: reportEnvFilesIn });
|
|
74
|
-
|
|
75
|
-
// Best-effort bounded-cache eviction (main thread only; the core guards on it).
|
|
76
|
-
maybeSweepCache();
|
|
77
|
-
|
|
78
|
-
// ── Resolve hook ────────────────────────────────────────────────────
|
|
79
|
-
function resolve(specifier, context, nextResolve) {
|
|
80
|
-
const r = resolveSpec(specifier, context.parentURL);
|
|
81
|
-
return r ?? nextResolve(specifier, context);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ── Load hook ───────────────────────────────────────────────────────
|
|
85
|
-
function load(url, context, nextLoad) {
|
|
86
|
-
const ext = extname(url);
|
|
87
|
-
|
|
88
|
-
// Watch mode: surface this file's nearest config files (tsconfig.json,
|
|
89
|
-
// package.json) + sibling `.env*` so edits to them restart the run. Done for
|
|
90
|
-
// every user file (not just transpiled ones) — getTsconfigForDir/getPackageType
|
|
91
|
-
// self-report via the injected watch hooks.
|
|
92
|
-
if (WATCH_REPORTING && url.startsWith("file:") && !isNodeModules(url)) {
|
|
93
|
-
try {
|
|
94
|
-
const dir = dirname(fileURLToPath(url));
|
|
95
|
-
getTsconfigForDir(dir);
|
|
96
|
-
getPackageType(dir);
|
|
97
|
-
} catch {}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (TRANSPILE_EXTS.has(ext)) return loadTranspile(url, ext);
|
|
101
|
-
if (ext in DATA_EXTS) return loadData(url, ext);
|
|
102
|
-
|
|
103
|
-
return nextLoad(url, context);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ── CommonJS require() augmentation (BOTH tiers) ────────────────────
|
|
107
|
-
// `module.registerHooks`' CJS-`require()` coverage is INCOMPLETE before ~Node 24:
|
|
108
|
-
// on Node 22.15 a `require()` from a `.cts` parent (which Node loads via the ESM
|
|
109
|
-
// translator's special-require) hits native Module._resolveFilename with no
|
|
110
|
-
// tsconfig/extensionless handling — a `require('@alias')` or `require('./x')` of a
|
|
111
|
-
// `.ts` target throws MODULE_NOT_FOUND, while the same code works on Node 26 (where
|
|
112
|
-
// registerHooks does cover it) and on the fast `import` path. On the compat tier
|
|
113
|
-
// (18.19–22.14) the only hook surface is `module.register`, which intercepts the
|
|
114
|
-
// ESM loader ONLY — so `require()` is entirely unaugmented there. Both gaps have
|
|
115
|
-
// the same closure: install this main-thread CJS shim, reusing the core's canonical
|
|
116
|
-
// resolveCjsPath / loadTranspile (no drift). It tries nub's resolution first and
|
|
117
|
-
// FALLS THROUGH to native on a miss, so it is a safe no-op on the versions where
|
|
118
|
-
// registerHooks already covers require (Node 24+/26). Mechanism stays within the
|
|
119
|
-
// augmenter rules: exactly what `--require`-installing the ts-node / tsx CJS shim
|
|
120
|
-
// has always done. So it runs on the fast tier too — registerHooks is necessary
|
|
121
|
-
// but, below ~Node 24, not sufficient for require().
|
|
122
|
-
// This error is surfaced ONLY on Node versions without native require(esm)
|
|
123
|
-
// (< 20.19 / 22.0–22.11), where require() of an ES module genuinely cannot work.
|
|
124
|
-
// On every require(esm)-capable Node, Node loads the ES module itself and this is
|
|
125
|
-
// never reached. The message is user-facing: no internal mechanism names.
|
|
126
|
-
function requireEsmError(filename) {
|
|
127
|
-
const err = new Error(
|
|
128
|
-
`Cannot require() this file — it is an ES module.\n` +
|
|
129
|
-
` ${filename}\n` +
|
|
130
|
-
`It uses \`import\`/\`export\`, so it loads as an ES module, and this version of ` +
|
|
131
|
-
`Node can't require() an ES module. Load it with \`import(...)\` instead, rename ` +
|
|
132
|
-
`it to .cts for a CommonJS module, or upgrade Node.`,
|
|
133
|
-
);
|
|
134
|
-
err.code = "ERR_REQUIRE_ESM";
|
|
135
|
-
return err;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// `withClassicTranspile` — also install the `require.extensions` (classic CommonJS
|
|
139
|
-
// loader) transpile hook. This is needed ONLY on Node WITHOUT native require(esm)
|
|
140
|
-
// (< 20.19 / 22.0–22.11): there, `module.register`'s ESM-loader hooks can't reach a
|
|
141
|
-
// `require()`, AND an ES module simply can't be require()d, so we transpile CJS
|
|
142
|
-
// content classically and surface a clean error for ESM content. On require(esm)-
|
|
143
|
-
// capable Node we DON'T install it — registering `require.extensions['.ts']` would
|
|
144
|
-
// shadow Node's own native require(esm) of ES-module `.ts` files (breaking
|
|
145
|
-
// `require("./esm.ts")`), and the resolve shim below plus the tier's load hook
|
|
146
|
-
// already cover resolution + transpile.
|
|
147
|
-
function installCjsRequireHooks(withClassicTranspile) {
|
|
148
|
-
// Module._resolveFilename: try nub's resolution first (tsconfig paths,
|
|
149
|
-
// extensionless .ts, .js→.ts). If the target is an ES-module TS file AND this
|
|
150
|
-
// Node can't require(esm), throw a clean error HERE — before Node's special-
|
|
151
|
-
// require (used for require() from a CJS module loaded via the ESM translator)
|
|
152
|
-
// crashes with an opaque `cjsCache.get(...)` TypeError. On require(esm)-capable
|
|
153
|
-
// Node, return the path and let Node load the ES module itself. On miss/own
|
|
154
|
-
// error, fall through to Node (augmentation never masks Node's diagnostics).
|
|
155
|
-
const origResolveFilename = module._resolveFilename;
|
|
156
|
-
module._resolveFilename = function (request, parent, isMain, options) {
|
|
157
|
-
let resolved = null;
|
|
158
|
-
try {
|
|
159
|
-
const parentPath = parent && typeof parent.filename === "string" ? parent.filename : null;
|
|
160
|
-
resolved = resolveCjsPath(request, parentPath);
|
|
161
|
-
} catch { /* fall through to Node */ }
|
|
162
|
-
if (resolved) {
|
|
163
|
-
if (withClassicTranspile && requireTargetIsEsm(resolved, pathExtname(resolved))) {
|
|
164
|
-
throw requireEsmError(resolved);
|
|
165
|
-
}
|
|
166
|
-
return resolved;
|
|
167
|
-
}
|
|
168
|
-
return origResolveFilename.call(this, request, parent, isMain, options);
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
if (!withClassicTranspile) return;
|
|
172
|
-
|
|
173
|
-
// require.extensions: transpile via the SAME loadTranspile the load hook uses —
|
|
174
|
-
// target:'es2022' lowering (`using`), tsconfig, source maps, the Stage-3
|
|
175
|
-
// decorator guard, and module-format detection are all identical to the fast
|
|
176
|
-
// tier. The path is already a real TS file (Module._resolveFilename ran first).
|
|
177
|
-
// A module-format source can't be _compile'd as CJS — same clean error as above.
|
|
178
|
-
const transpileExtension = (mod, filename) => {
|
|
179
|
-
const { source, format } = loadTranspile(pathToFileURL(filename).href, pathExtname(filename));
|
|
180
|
-
if (format === "module") throw requireEsmError(filename);
|
|
181
|
-
mod._compile(source, filename);
|
|
182
|
-
};
|
|
183
|
-
for (const ext of [".ts", ".cts", ".mts", ".tsx", ".jsx"]) {
|
|
184
|
-
module._extensions[ext] = transpileExtension;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ── Pre-load clobbered polyfill packages BEFORE hooks register ──────
|
|
189
|
-
// Packages in the core's CLOBBER_MAP can't be imported after hooks register
|
|
190
|
-
// because the resolve hook returns a synthetic module instead of the real
|
|
191
|
-
// package. Load them here via CJS require (not yet hooked) and stash them.
|
|
192
|
-
// Temporal is the exception (A37): the polyfill is ~18ms to load and most scripts
|
|
193
|
-
// never touch it, so we only RESOLVE its path now (cheap) and defer the load to a
|
|
194
|
-
// lazy global getter (below). Requiring it later by absolute path bypasses the
|
|
195
|
-
// CLOBBER_MAP resolve-hook entry, which keys on the "@js-temporal/polyfill"
|
|
196
|
-
// specifier — not the resolved path.
|
|
197
|
-
const __preloadedPolyfills = {};
|
|
198
|
-
// Feature-detect before requiring (A39): URLPattern is native on Node 24+, so
|
|
199
|
-
// skip loading the polyfill there. On 22.x it's absent → load it.
|
|
200
|
-
if (typeof globalThis.URLPattern === "undefined") {
|
|
201
|
-
try { __preloadedPolyfills.urlpattern = __require("urlpattern-polyfill"); } catch {}
|
|
202
|
-
}
|
|
203
|
-
// Float16Array: native on Node 24+, absent on the 22.x floor.
|
|
204
|
-
if (typeof globalThis.Float16Array === "undefined") {
|
|
205
|
-
try { __preloadedPolyfills.float16 = __require("@petamoriken/float16"); } catch {}
|
|
206
|
-
}
|
|
207
|
-
let __temporalPath;
|
|
208
|
-
try { __temporalPath = __require.resolve("@js-temporal/polyfill"); } catch {}
|
|
209
|
-
|
|
210
|
-
// ── Tier dispatch ───────────────────────────────────────────────────
|
|
211
|
-
// Three tiers per wiki/research/supported-node-versions.md:
|
|
212
|
-
// - >= 22.15.0: sync module.registerHooks (fast path, in-thread)
|
|
213
|
-
// - >= 18.19.0 < 22.15: async module.register (loader worker) + main-thread
|
|
214
|
-
// CJS require() shim (module.register is ESM-only)
|
|
215
|
-
// - < 18.19.0: unsupported (Rust spawn path hard-errors; defensive log)
|
|
216
|
-
//
|
|
217
|
-
// No user-visible distinction between the two augmentation tiers — both deliver
|
|
218
|
-
// the identical feature surface, silently. The compat-mode notice was dropped
|
|
219
|
-
// 2026-05-29 (it labeled a purely internal mechanism choice).
|
|
220
|
-
const __nodeVersionParts = process.versions.node.split(".").map((n) => parseInt(n, 10));
|
|
221
|
-
const __nodeMajor = __nodeVersionParts[0] || 0;
|
|
222
|
-
const __nodeMinor = __nodeVersionParts[1] || 0;
|
|
223
|
-
const __isAugmentedTier = __nodeMajor > 22 || (__nodeMajor === 22 && __nodeMinor >= 15);
|
|
224
|
-
const __isCompatTier = !__isAugmentedTier && (__nodeMajor > 18 || (__nodeMajor === 18 && __nodeMinor >= 19));
|
|
225
|
-
// Native TypeScript support (`process.features.typescript` is "strip"/"transform"
|
|
226
|
-
// — Node 22.18+/24+). Where present, Node loads `.ts` from require() itself,
|
|
227
|
-
// INCLUDING ES-module `.ts` via native require(esm); registering the classic
|
|
228
|
-
// `require.extensions['.ts']` shim there would shadow that and break
|
|
229
|
-
// `require("./esm.ts")`. Where absent (≤ 22.17 / the whole compat tier), Node can't
|
|
230
|
-
// load a required `.ts` on its own, so the shim is required — it transpiles CJS
|
|
231
|
-
// content and surfaces a clean error for ES-module content (which require() can't
|
|
232
|
-
// load there anyway). Feature-detected, not version-gated.
|
|
33
|
+
const common = __require("./preload-common.cjs");
|
|
34
|
+
const { installSyncPolyfills } = __require("./polyfills.cjs");
|
|
35
|
+
|
|
36
|
+
// ── Tier detection ──────────────────────────────────────────────────
|
|
37
|
+
// This `.mjs` preload should only ever be `--import`ed for the compat tier (the
|
|
38
|
+
// Rust spawn path chooses `--require preload.cjs` for 22.15+). But guard anyway: if
|
|
39
|
+
// someone `--import`s it directly on an unsupported Node, emit a clear message and
|
|
40
|
+
// skip hook registration rather than throw (throwing breaks user-invoked --import
|
|
41
|
+
// flows). The fast-tier branch is intentionally absent here — 22.15+ goes through
|
|
42
|
+
// preload.cjs.
|
|
43
|
+
const [__major = 0, __minor = 0] = process.versions.node.split(".").map((n) => parseInt(n, 10));
|
|
44
|
+
const __isCompatTier = __major > 18 || (__major === 18 && __minor >= 19);
|
|
45
|
+
const __isFastTier = __major > 22 || (__major === 22 && __minor >= 15);
|
|
46
|
+
|
|
47
|
+
// Native TypeScript support (`process.features.typescript`). Where absent (the
|
|
48
|
+
// whole compat tier ≤ 22.17), Node can't load a required `.ts` on its own, so the
|
|
49
|
+
// classic require.extensions transpile shim is installed; where present it's
|
|
50
|
+
// skipped so Node's native require() of `.ts` isn't shadowed.
|
|
233
51
|
const __hasNativeTs = !!process.features?.typescript;
|
|
234
52
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
|
|
53
|
+
// Watch reporting + the Temporal lazy global are tier-independent.
|
|
54
|
+
common.installWatchReporting(core);
|
|
55
|
+
|
|
56
|
+
if (__isFastTier) {
|
|
57
|
+
// Defensive only — the Rust path uses preload.cjs for 22.15+. If reached, the
|
|
58
|
+
// sync registerHooks API is available; register synchronously to stay correct.
|
|
59
|
+
const { resolve, load } = common.makeHooks(core, process.env.WATCH_REPORT_DEPENDENCIES === "1");
|
|
242
60
|
module.registerHooks({ resolve, load });
|
|
243
|
-
installCjsRequireHooks(!__hasNativeTs);
|
|
61
|
+
common.installCjsRequireHooks(core, !__hasNativeTs);
|
|
244
62
|
} else if (__isCompatTier) {
|
|
245
63
|
// Compat path: ESM `import` hooks run in a dedicated loader worker thread.
|
|
246
64
|
module.register("./preload-async-hooks.mjs", import.meta.url);
|
|
247
65
|
// The main-thread CommonJS require() shim's format detection is synchronous and
|
|
248
|
-
// reaches down to Node 18.19, where `require("oxc-parser")` (ESM-only) fails —
|
|
249
|
-
//
|
|
250
|
-
await ensureParser();
|
|
66
|
+
// reaches down to Node 18.19, where `require("oxc-parser")` (ESM-only) fails — so
|
|
67
|
+
// preload the parser via dynamic import now, while we can still await.
|
|
68
|
+
await core.ensureParser();
|
|
251
69
|
// module.register() is ESM-loader-only; augment CommonJS require() on the main
|
|
252
70
|
// thread too. The compat tier never has native `.ts`, so it always installs the
|
|
253
71
|
// classic transpile shim (CJS transpile + clean error for ES-module require()).
|
|
254
|
-
installCjsRequireHooks(!__hasNativeTs);
|
|
72
|
+
common.installCjsRequireHooks(core, !__hasNativeTs);
|
|
255
73
|
} else {
|
|
256
|
-
// Defensive: Rust spawn path hard-errors on < 18.19 before reaching here. If
|
|
257
|
-
// invoked directly via `node --import`, emit a clear message and skip hook
|
|
258
|
-
// registration rather than throw (throwing breaks user-invoked --import flows).
|
|
259
74
|
process.stderr.write(
|
|
260
75
|
`Nub requires Node 18.19 or newer for runtime augmentation; got ${process.versions.node}. Preload is inactive.\n`,
|
|
261
76
|
);
|
|
262
77
|
}
|
|
263
78
|
|
|
264
|
-
// ──
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// it (by resolved path, bypassing the clobber) on first access. The CLOBBER
|
|
274
|
-
// re-export of `import "@js-temporal/polyfill"` reads globalThis.Temporal, so a
|
|
275
|
-
// user importing the package triggers this same one-time load. A plain assignment
|
|
276
|
-
// (`globalThis.Temporal = …`) replaces the accessor with the value.
|
|
277
|
-
if (typeof globalThis.Temporal === "undefined" && __temporalPath) {
|
|
278
|
-
const defineTemporal = (value) =>
|
|
279
|
-
Object.defineProperty(globalThis, "Temporal", {
|
|
280
|
-
value,
|
|
281
|
-
configurable: true,
|
|
282
|
-
writable: true,
|
|
283
|
-
enumerable: false,
|
|
284
|
-
});
|
|
285
|
-
Object.defineProperty(globalThis, "Temporal", {
|
|
286
|
-
configurable: true,
|
|
287
|
-
enumerable: false,
|
|
288
|
-
get() {
|
|
289
|
-
const polyfill = __require(__temporalPath);
|
|
290
|
-
// @js-temporal/polyfill exports `toTemporalInstant` as a function but does
|
|
291
|
-
// NOT auto-install it on Date.prototype (you assign it yourself). Install it
|
|
292
|
-
// here so that on the floor (no native Temporal) `date.toTemporalInstant()`
|
|
293
|
-
// AND the package clobber's re-export of `Date.prototype.toTemporalInstant`
|
|
294
|
-
// both work — matching native Node. Guarded so we never replace a native
|
|
295
|
-
// implementation on a runtime that ships Temporal.
|
|
296
|
-
if (
|
|
297
|
-
typeof Date.prototype.toTemporalInstant !== "function" &&
|
|
298
|
-
typeof polyfill.toTemporalInstant === "function"
|
|
299
|
-
) {
|
|
300
|
-
Object.defineProperty(Date.prototype, "toTemporalInstant", {
|
|
301
|
-
value: polyfill.toTemporalInstant,
|
|
302
|
-
configurable: true,
|
|
303
|
-
writable: true,
|
|
304
|
-
enumerable: false,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
const T = polyfill.Temporal;
|
|
308
|
-
defineTemporal(T);
|
|
309
|
-
return T;
|
|
310
|
-
},
|
|
311
|
-
set: defineTemporal,
|
|
312
|
-
});
|
|
79
|
+
// ── Clobbered-polyfill preloading + polyfills ───────────────────────
|
|
80
|
+
// Require the clobbered polyfill packages before the resolve hook would intercept
|
|
81
|
+
// them, then install the sync globals (shared with the fast tier) and the two ESM
|
|
82
|
+
// side-effect modules. On the compat tier `require(esm)` of the worker/navigator
|
|
83
|
+
// modules is unreliable, so they load via dynamic `import()` here.
|
|
84
|
+
const __preloadedPolyfills = common.preloadPolyfillPackages(__require);
|
|
85
|
+
installSyncPolyfills(__preloadedPolyfills);
|
|
86
|
+
if (typeof globalThis.navigator?.locks === "undefined") {
|
|
87
|
+
await import("./navigator-locks.mjs");
|
|
313
88
|
}
|
|
89
|
+
await import("./worker-polyfill.mjs");
|
|
314
90
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
91
|
+
// ── Temporal: lazy global (A37) ─────────────────────────────────────
|
|
92
|
+
common.installTemporalLazyGlobal(__require);
|
|
93
|
+
|
|
94
|
+
// ── Compile-cache: re-enable for the USER's modules (R8) ────────────
|
|
95
|
+
// spawn.rs strips NODE_COMPILE_CACHE for every augmented spawn (both tiers), so
|
|
96
|
+
// nub's preload chain is never cached into the user's dir. Re-point the cache at
|
|
97
|
+
// the user's original dir (via the PID-keyed sentinel) so their own modules still
|
|
98
|
+
// cache. `module.enableCompileCache` only exists on Node 22.8+, so on most of the
|
|
99
|
+
// compat tier (< 22.8) this is a safe no-op; it benefits 22.8–22.14 users who set
|
|
100
|
+
// NODE_COMPILE_CACHE. See reenableUserCompileCache for the full rationale.
|
|
101
|
+
common.reenableUserCompileCache();
|
|
@@ -20,18 +20,119 @@
|
|
|
20
20
|
// top-level hook registration here — importing this module never augments the
|
|
21
21
|
// realm; the tier files do that.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
// EVERY dependency of this module is pulled in via CJS `require()` (below), NOT
|
|
24
|
+
// via static ESM `import`. This is load-bearing for loader compatibility (R11):
|
|
25
|
+
// nub loads transform-core through `require(esm)`, and Node's `require(esm)`
|
|
26
|
+
// instantiates the module by walking its STATIC IMPORT graph through whatever ESM
|
|
27
|
+
// loader hooks are registered — including the USER's `--loader`/`register()`
|
|
28
|
+
// chain. Static `import oxc-transform`/`get-tsconfig`/`./version.mjs`/`node:*`
|
|
29
|
+
// here therefore leaked nub's entire internal graph (transform-core, version.mjs,
|
|
30
|
+
// oxc-transform, get-tsconfig, their transitive node_modules deps, and the node:
|
|
31
|
+
// builtins) THROUGH the user's resolve/load hooks, which observed and corrupted
|
|
32
|
+
// it (a user load hook returning `source: 1` for version.mjs, a resolve hook
|
|
33
|
+
// appending `?foo` to `oxc-transform`, a strict loader throwing on the bare
|
|
34
|
+
// `oxc-transform` specifier — see test-esm-loader-chaining, -example-loader,
|
|
35
|
+
// -preserve-symlinks-not-found, test-shadow-realm-custom-loaders). Verified: a CJS
|
|
36
|
+
// `require()` of a package/builtin does NOT route through the ESM loader chain, so
|
|
37
|
+
// loading the graph this way bypasses the user chain entirely. `process
|
|
38
|
+
// .getBuiltinModule` fetches node: builtins synchronously off the loader chain;
|
|
39
|
+
// `createRequire(import.meta.url)` resolves the bare deps from nub's distribution.
|
|
40
|
+
// This file keeps its `export`s (it stays an ES module), but has ZERO static
|
|
41
|
+
// imports, so `require(esm)` finds no dependency graph to route through the user.
|
|
42
|
+
const { createRequire } = process.getBuiltinModule("node:module");
|
|
33
43
|
const __require = createRequire(import.meta.url);
|
|
34
44
|
|
|
45
|
+
const module = process.getBuiltinModule("node:module");
|
|
46
|
+
const { readFileSync, writeFileSync, mkdirSync, statSync, renameSync, unlinkSync, readdirSync } = process.getBuiltinModule("node:fs");
|
|
47
|
+
const { fileURLToPath, pathToFileURL } = process.getBuiltinModule("node:url");
|
|
48
|
+
const { join, dirname, resolve: pathResolve, extname: pathExtname } = process.getBuiltinModule("node:path");
|
|
49
|
+
// oxc-transform is `type: module`, so a plain `require("oxc-transform")` uses
|
|
50
|
+
// `require(esm)` and Node instantiates its ESM entry by walking ITS static
|
|
51
|
+
// imports through the registered ESM loader chain — i.e. through the USER's
|
|
52
|
+
// `--loader` worker (which nub's main-thread sync hooks do NOT chain into, so the
|
|
53
|
+
// resolveSpec short-circuit can't catch it). oxc-transform's entry has exactly one
|
|
54
|
+
// ESM-only construct that leaks: `import { createRequire } from 'node:module'`
|
|
55
|
+
// (the rest is CJS `require`/`module.exports`-shaped). To keep it off the chain
|
|
56
|
+
// entirely we load it AS CommonJS: read the entry source, neutralize its tiny ESM
|
|
57
|
+
// header (the `import { createRequire }` line, the `const require =
|
|
58
|
+
// createRequire(import.meta.url)` shadow, the `import.meta.url`-based `__dirname`)
|
|
59
|
+
// and convert its `export { X }` footer to `module.exports`, then compile it with
|
|
60
|
+
// `module._compile` at its real path so its internal `require('./binding.node')`
|
|
61
|
+
// and `__dirname` resolve correctly. A CJS-compiled module has no static-import
|
|
62
|
+
// graph for Node to route through any loader. Falls back to plain require(esm) if
|
|
63
|
+
// the source shape ever changes (then the leak returns, but transpilation works).
|
|
64
|
+
function requireOxcAsCjs() {
|
|
65
|
+
const Module = module;
|
|
66
|
+
let entry;
|
|
67
|
+
try {
|
|
68
|
+
entry = __require.resolve("oxc-transform");
|
|
69
|
+
} catch {
|
|
70
|
+
return __require("oxc-transform");
|
|
71
|
+
}
|
|
72
|
+
// Only the .js NAPI-RS wrapper has the mixed ESM-header/CJS-body shape we can
|
|
73
|
+
// safely transform; anything else, defer to the normal loader.
|
|
74
|
+
if (!entry.endsWith(".js")) return __require("oxc-transform");
|
|
75
|
+
try {
|
|
76
|
+
let src = readFileSync(entry, "utf8");
|
|
77
|
+
// Strip the ESM header: `import { createRequire } from 'node:module'`,
|
|
78
|
+
// `const require = createRequire(import.meta.url)`, and any
|
|
79
|
+
// `const __dirname = new URL('.', import.meta.url).pathname`. In a CJS module
|
|
80
|
+
// `require`, `__dirname`, `__filename` are already injected by _compile.
|
|
81
|
+
src = src
|
|
82
|
+
.replace(/^\s*import\s*\{[^}]*\}\s*from\s*['"]node:module['"];?\s*$/m, "")
|
|
83
|
+
.replace(/^\s*const\s+require\s*=\s*createRequire\([^)]*\);?\s*$/m, "")
|
|
84
|
+
.replace(/^\s*const\s+__dirname\s*=\s*new URL\([^)]*\)\.pathname;?\s*$/m, "");
|
|
85
|
+
// Convert the `export { Name }` footer lines to a single CJS exports block.
|
|
86
|
+
const names = [];
|
|
87
|
+
src = src.replace(/^\s*export\s*\{\s*([A-Za-z0-9_$]+)\s*\}\s*;?\s*$/gm, (_m, n) => {
|
|
88
|
+
names.push(n);
|
|
89
|
+
return "";
|
|
90
|
+
});
|
|
91
|
+
// Guard: if any `import`/`export`/`import.meta` survived, we don't understand
|
|
92
|
+
// this version's shape — bail to the safe loader rather than ship broken code.
|
|
93
|
+
if (/^\s*(import|export)\s/m.test(src) || /\bimport\.meta\b/.test(src)) {
|
|
94
|
+
return __require("oxc-transform");
|
|
95
|
+
}
|
|
96
|
+
if (names.length === 0) return __require("oxc-transform");
|
|
97
|
+
src += `\nmodule.exports = { ${names.join(", ")} };\n`;
|
|
98
|
+
const m = new Module(entry, null);
|
|
99
|
+
m.filename = entry;
|
|
100
|
+
m.paths = Module._nodeModulePaths(dirname(entry));
|
|
101
|
+
m._compile(src, entry);
|
|
102
|
+
return m.exports;
|
|
103
|
+
} catch {
|
|
104
|
+
return __require("oxc-transform");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const { transformSync } = requireOxcAsCjs();
|
|
108
|
+
const { getTsconfig, createPathsMatcher } = __require("get-tsconfig");
|
|
109
|
+
|
|
110
|
+
// NUB_VERSION is the single source of truth in runtime/version.mjs. We must NOT
|
|
111
|
+
// `import` it (that would route version.mjs through the user loader chain — see
|
|
112
|
+
// above; a user load hook returning bogus source corrupts it), and we cannot
|
|
113
|
+
// `require()` it either (it is an ES module, so `require()` uses require(esm),
|
|
114
|
+
// which re-routes version.mjs's own load through the chain). Instead read its
|
|
115
|
+
// text directly and extract the literal — `make version` keeps the assignment on
|
|
116
|
+
// one line (`export const NUB_VERSION = "x.y.z";`), so a tight regex is stable.
|
|
117
|
+
const NUB_VERSION = (() => {
|
|
118
|
+
try {
|
|
119
|
+
const text = readFileSync(fileURLToPath(new URL("./version.mjs", import.meta.url)), "utf8");
|
|
120
|
+
const m = text.match(/NUB_VERSION\s*=\s*["']([^"']+)["']/);
|
|
121
|
+
if (m) return m[1];
|
|
122
|
+
} catch {}
|
|
123
|
+
return "0.0.0";
|
|
124
|
+
})();
|
|
125
|
+
|
|
126
|
+
// `node:crypto` is used ONLY to hash the transpile-cache key, so it loads lazily
|
|
127
|
+
// on first transpile rather than at module top level. Importing it eagerly pulls
|
|
128
|
+
// in the crypto/tls native tree (~dozens of builtins) on EVERY startup — including
|
|
129
|
+
// a plain-JS run that never transpiles anything (R7). The first `.ts` transpile
|
|
130
|
+
// pays the one-time require; a no-TS run never touches it. Memoized.
|
|
131
|
+
let _createHash = null;
|
|
132
|
+
function getCreateHash() {
|
|
133
|
+
return (_createHash ??= __require("node:crypto").createHash);
|
|
134
|
+
}
|
|
135
|
+
|
|
35
136
|
// ── Constants ───────────────────────────────────────────────────────
|
|
36
137
|
export const TRANSPILE_EXTS = new Set([".ts", ".tsx", ".mts", ".cts", ".jsx"]);
|
|
37
138
|
export const DATA_EXTS = { ".jsonc": "jsonc", ".json5": "json5", ".toml": "toml", ".yaml": "yaml", ".yml": "yaml", ".txt": "txt" };
|
|
@@ -263,10 +364,102 @@ export function getProbeOrder(parentExt) {
|
|
|
263
364
|
}
|
|
264
365
|
}
|
|
265
366
|
|
|
367
|
+
// nub's own runtime directory (this file's dir, as a file: URL prefix). Any
|
|
368
|
+
// resolution whose IMPORTER lives here is one of nub's internal requires — the
|
|
369
|
+
// preload loading transform-core, transform-core requiring oxc, the Temporal lazy
|
|
370
|
+
// getter resolving @js-temporal/polyfill — and must NEVER be routed through nub's
|
|
371
|
+
// own clobber/vendored/tsconfig logic: those are user-code conveniences, and
|
|
372
|
+
// applying them to nub's internals both breaks them (e.g. the Temporal clobber
|
|
373
|
+
// re-exports globalThis.Temporal, which IS the getter → a require of the polyfill
|
|
374
|
+
// from the getter would recurse into the clobber) and amplifies the user loader
|
|
375
|
+
// chain by re-walking nub's internal graph through user hooks (R11). Short-circuit
|
|
376
|
+
// to native resolution for these.
|
|
377
|
+
const RUNTIME_DIR_URL = new URL(".", import.meta.url).href;
|
|
378
|
+
|
|
379
|
+
// nub's internal-graph package roots — the file: URL prefixes of the npm packages
|
|
380
|
+
// nub itself loads (oxc-transform, get-tsconfig) and their transitive deps. Any
|
|
381
|
+
// resolution whose IMPORTER lives under one of these is part of nub's internal
|
|
382
|
+
// graph, NOT user code. `oxc-transform` is `type: module`, so even when nub pulls
|
|
383
|
+
// it in via CJS `require()`, Node loads its `index.js` through `require(esm)` and
|
|
384
|
+
// walks ITS static `import "node:module"` graph through the registered ESM loader
|
|
385
|
+
// chain — including the USER's loader. That re-leaks nub internals one hop down
|
|
386
|
+
// (R11): test-esm-example-loader's strict loader throws on `node:module`,
|
|
387
|
+
// loader-resolve-shortcircuit appends `?foo` to oxc's specifiers, etc. So a
|
|
388
|
+
// nub-internal-graph resolution must FULLY short-circuit (resolve natively, return
|
|
389
|
+
// shortCircuit:true) — never delegate to nextResolve — for EVERY specifier,
|
|
390
|
+
// including node: builtins and the package's own relative imports. Computed lazily
|
|
391
|
+
// (and pinned even on resolve failure) so a missing dep can't wedge startup.
|
|
392
|
+
let _nubGraphRoots = null;
|
|
393
|
+
function nubGraphRoots() {
|
|
394
|
+
if (_nubGraphRoots) return _nubGraphRoots;
|
|
395
|
+
const roots = [];
|
|
396
|
+
for (const pkg of ["oxc-transform", "get-tsconfig"]) {
|
|
397
|
+
try {
|
|
398
|
+
const entry = __require.resolve(pkg);
|
|
399
|
+
// Package root = the directory two levels up does not work generically;
|
|
400
|
+
// instead key on the package-name segment: everything under
|
|
401
|
+
// `.../node_modules/<pkg>/` is that package. Use the entry's dir-with-pkg.
|
|
402
|
+
const idx = entry.lastIndexOf(`${sep()}node_modules${sep()}`);
|
|
403
|
+
if (idx !== -1) {
|
|
404
|
+
// Keep through the package-name segment (handles scoped names too).
|
|
405
|
+
const afterNM = entry.slice(idx + (`${sep()}node_modules${sep()}`).length);
|
|
406
|
+
const firstSeg = afterNM.startsWith("@")
|
|
407
|
+
? afterNM.split(sep()).slice(0, 2).join(sep())
|
|
408
|
+
: afterNM.split(sep())[0];
|
|
409
|
+
const pkgRoot = entry.slice(0, idx) + `${sep()}node_modules${sep()}` + firstSeg + sep();
|
|
410
|
+
roots.push(pathToFileURL(pkgRoot).href);
|
|
411
|
+
}
|
|
412
|
+
} catch {}
|
|
413
|
+
}
|
|
414
|
+
return (_nubGraphRoots = roots);
|
|
415
|
+
}
|
|
416
|
+
function sep() {
|
|
417
|
+
return process.platform === "win32" ? "\\" : "/";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Is this importer part of nub's own internal module graph (runtime dir or a nub
|
|
421
|
+
// dependency package)? Such imports must bypass the user ESM loader chain entirely.
|
|
422
|
+
function isNubInternalParent(parentURL) {
|
|
423
|
+
if (!parentURL) return false;
|
|
424
|
+
const p = String(parentURL);
|
|
425
|
+
if (p.startsWith(RUNTIME_DIR_URL)) return true;
|
|
426
|
+
for (const root of nubGraphRoots()) {
|
|
427
|
+
if (p.startsWith(root)) return true;
|
|
428
|
+
}
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
|
|
266
432
|
// Resolve a specifier the way both hook tiers do. Returns `{ url, shortCircuit }`
|
|
267
433
|
// to short-circuit Node's resolver, or `null` to fall through to `nextResolve`.
|
|
268
434
|
// `parentURL` is the importer (a file: URL string), or "" for the entry.
|
|
269
435
|
export function resolveSpec(specifier, parentURL) {
|
|
436
|
+
// nub's own internal graph (importer inside nub's runtime dir OR a nub
|
|
437
|
+
// dependency package): resolve natively and SHORT-CIRCUIT so nextResolve (the
|
|
438
|
+
// user's loader chain) never observes nub's internals. This MUST run before the
|
|
439
|
+
// node:/data:/builtin early-returns below, because those `return null` =
|
|
440
|
+
// DELEGATE to the user loader — and a nub-internal `import "node:module"` (e.g.
|
|
441
|
+
// from oxc-transform's ESM entry) delegated to a strict user loader is exactly
|
|
442
|
+
// the R11 leak. See isNubInternalParent / nubGraphRoots.
|
|
443
|
+
if (isNubInternalParent(parentURL)) {
|
|
444
|
+
if (specifier.startsWith("node:") || module.builtinModules.includes(specifier)) {
|
|
445
|
+
const url = specifier.startsWith("node:") ? specifier : `node:${specifier}`;
|
|
446
|
+
return { url, shortCircuit: true };
|
|
447
|
+
}
|
|
448
|
+
if (specifier.startsWith("data:")) return { url: specifier, shortCircuit: true };
|
|
449
|
+
// A relative/bare import from inside nub's graph: resolve it natively from the
|
|
450
|
+
// parent's own require() resolver (NOT nub's tsconfig/clobber/probe logic) and
|
|
451
|
+
// short-circuit. Bare specifiers resolve from the parent package's location.
|
|
452
|
+
try {
|
|
453
|
+
const parentReq = createRequire(parentURL);
|
|
454
|
+
const resolved = parentReq.resolve(specifier);
|
|
455
|
+
return { url: pathToFileURL(resolved).href, shortCircuit: true };
|
|
456
|
+
} catch {
|
|
457
|
+
// Couldn't resolve from the parent (e.g. a non-file: parent): still short-
|
|
458
|
+
// circuit by handing the specifier back as-is, so the user chain is bypassed.
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
270
463
|
// node: and data: protocols, and bare Node built-ins, are never ours.
|
|
271
464
|
if (specifier.startsWith("node:") || specifier.startsWith("data:")) return null;
|
|
272
465
|
if (module.builtinModules.includes(specifier)) return null;
|
|
@@ -544,7 +737,7 @@ if (!CACHE_DISABLED) {
|
|
|
544
737
|
}
|
|
545
738
|
|
|
546
739
|
function cacheKey(source) {
|
|
547
|
-
return
|
|
740
|
+
return getCreateHash()("sha256")
|
|
548
741
|
.update(NUB_VERSION).update("\0")
|
|
549
742
|
.update(CACHE_SCHEMA).update("\0")
|
|
550
743
|
.update(source)
|
|
@@ -557,7 +750,7 @@ function cacheKey(source) {
|
|
|
557
750
|
// garbage to V8.
|
|
558
751
|
const CACHE_INTEGRITY_LEN = 16;
|
|
559
752
|
function cacheIntegrity(body) {
|
|
560
|
-
return
|
|
753
|
+
return getCreateHash()("sha256").update(body).digest("hex").slice(0, CACHE_INTEGRITY_LEN);
|
|
561
754
|
}
|
|
562
755
|
function cacheGet(key) {
|
|
563
756
|
if (!cacheDir) return null;
|
|
@@ -695,6 +888,14 @@ export function loadTranspile(url, ext) {
|
|
|
695
888
|
map.sourcesContent = [source];
|
|
696
889
|
code += `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(map)).toString("base64")}\n`;
|
|
697
890
|
}
|
|
891
|
+
// Append a `//# sourceURL=` magic comment, matching Node's native strip-types
|
|
892
|
+
// (lib/internal/modules/typescript.js: `return ${code}\n\n//# sourceURL=${filename}`).
|
|
893
|
+
// This is the marker V8/the inspector reads to set `scriptParsed.hasSourceURL =
|
|
894
|
+
// true` — the signal that a script is generated/transpiled rather than read
|
|
895
|
+
// verbatim from disk (test-inspector-strip-types asserts it). It coexists with
|
|
896
|
+
// the inline sourceMappingURL above (maps still drive stack frames); sourceURL
|
|
897
|
+
// only names the origin. Use the absolute file path, exactly as Node does.
|
|
898
|
+
code += `\n//# sourceURL=${filePath}\n`;
|
|
698
899
|
|
|
699
900
|
// Store the chosen format as a leading byte so cache hits skip re-detection.
|
|
700
901
|
cacheSet(key, (format === "commonjs" ? "c" : "m") + code);
|
package/runtime/version.mjs
CHANGED
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
// previously lived as a literal inside preload.mjs, which `make version` patched,
|
|
10
10
|
// while the worker carried a hand-maintained "…-compat" copy that `make version`
|
|
11
11
|
// never touched — a latent staleness bug this module closes.)
|
|
12
|
-
export const NUB_VERSION = "0.0.
|
|
12
|
+
export const NUB_VERSION = "0.0.13";
|
|
@@ -2,28 +2,52 @@
|
|
|
2
2
|
// Wraps node:worker_threads.Worker with EventTarget inheritance,
|
|
3
3
|
// real MessageEvent/ErrorEvent, and URL-only constructor (Deno shape).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import
|
|
5
|
+
// node: builtins are fetched via `process.getBuiltinModule` rather than static
|
|
6
|
+
// `import`. This file is loaded via `require(esm)` from the preload, and Node's
|
|
7
|
+
// `require(esm)` instantiates an ES module by walking its STATIC IMPORT graph
|
|
8
|
+
// through whatever ESM loader chain is registered — including the USER's
|
|
9
|
+
// `--loader`/`register()` hooks. A static `import { Worker } from
|
|
10
|
+
// "node:worker_threads"` therefore routes the builtin through the user chain; a
|
|
11
|
+
// user load hook that returns SOURCE for node:worker_threads makes V8 see no
|
|
12
|
+
// `Worker` export, so `new NodeWorker(...)` references an undefined binding and
|
|
13
|
+
// the child crashes (observed against test-esm-loader-chaining). `process
|
|
14
|
+
// .getBuiltinModule` fetches the real builtin synchronously off the loader
|
|
15
|
+
// graph, bypassing the user chain entirely — same fix transform-core.mjs uses.
|
|
16
|
+
const { Worker: NodeWorker, parentPort, isMainThread } = process.getBuiltinModule("node:worker_threads");
|
|
17
|
+
const { fileURLToPath } = process.getBuiltinModule("node:url");
|
|
7
18
|
|
|
8
19
|
// `ErrorEvent` only became a global in Node 26. On the 22/24 floor it is
|
|
9
20
|
// undefined, so `new ErrorEvent(...)` below would throw a ReferenceError inside
|
|
10
21
|
// the worker's "error" handler — crashing the PARENT thread on every worker
|
|
11
|
-
// that throws. Resolve the constructor
|
|
12
|
-
// present, otherwise a minimal Event subclass carrying the
|
|
13
|
-
// fields (message/error/filename/lineno/colno).
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// that throws. Resolve the constructor lazily and memoize on first use: use the
|
|
23
|
+
// native global when present, otherwise a minimal Event subclass carrying the
|
|
24
|
+
// standard ErrorEvent fields (message/error/filename/lineno/colno).
|
|
25
|
+
// See wiki/research/worker-polyfill.md.
|
|
26
|
+
//
|
|
27
|
+
// LAZY (not resolved at module load) on purpose: reading `globalThis.ErrorEvent`
|
|
28
|
+
// at top level trips Node's lazy `ErrorEvent` getter, which eagerly realizes
|
|
29
|
+
// ~100+ builtins (http2/tls/crypto/zlib/perf_hooks/webstreams) on EVERY startup
|
|
30
|
+
// — a cold-start regression (process.moduleLoadList ~230 vs node's ~110) that
|
|
31
|
+
// contradicts nub's fast-runner premise for the common "run a plain file, never
|
|
32
|
+
// touch Workers" case. The constructors are only ever needed inside the
|
|
33
|
+
// post-construction error/message handlers, so deferring resolution there costs
|
|
34
|
+
// nothing for non-Worker programs and nothing measurable for Worker ones.
|
|
35
|
+
let _ErrorEventCtor;
|
|
36
|
+
function getErrorEventCtor() {
|
|
37
|
+
return (_ErrorEventCtor ??=
|
|
38
|
+
typeof globalThis.ErrorEvent === "function"
|
|
39
|
+
? globalThis.ErrorEvent
|
|
40
|
+
: class ErrorEvent extends Event {
|
|
41
|
+
constructor(type, init = {}) {
|
|
42
|
+
super(type, init);
|
|
43
|
+
this.message = init.message ?? "";
|
|
44
|
+
this.error = init.error ?? null;
|
|
45
|
+
this.filename = init.filename ?? "";
|
|
46
|
+
this.lineno = init.lineno ?? 0;
|
|
47
|
+
this.colno = init.colno ?? 0;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
27
51
|
|
|
28
52
|
if (typeof globalThis.Worker === "undefined") {
|
|
29
53
|
class Worker extends EventTarget {
|
|
@@ -66,6 +90,7 @@ if (typeof globalThis.Worker === "undefined") {
|
|
|
66
90
|
});
|
|
67
91
|
|
|
68
92
|
this.#worker.on("error", (err) => {
|
|
93
|
+
const ErrorEventCtor = getErrorEventCtor();
|
|
69
94
|
this.dispatchEvent(new ErrorEventCtor("error", { error: err, message: err.message }));
|
|
70
95
|
});
|
|
71
96
|
|
|
@@ -107,7 +132,16 @@ if (typeof globalThis.Worker === "undefined") {
|
|
|
107
132
|
}
|
|
108
133
|
}
|
|
109
134
|
|
|
110
|
-
globalThis
|
|
135
|
+
// NON-ENUMERABLE: invisible to `Object.keys(globalThis)` / for-in is the
|
|
136
|
+
// additive contract — vanilla-Node code that enumerates the global object must
|
|
137
|
+
// not observe nub's injected `Worker`. Node defines its own globals the same
|
|
138
|
+
// way. Writable+configurable so user code can still override it.
|
|
139
|
+
Object.defineProperty(globalThis, "Worker", {
|
|
140
|
+
value: Worker,
|
|
141
|
+
enumerable: false,
|
|
142
|
+
writable: true,
|
|
143
|
+
configurable: true,
|
|
144
|
+
});
|
|
111
145
|
}
|
|
112
146
|
|
|
113
147
|
// Worker-side bootstrap: emulate the DedicatedWorkerGlobalScope on top of
|
|
@@ -118,7 +152,22 @@ if (typeof globalThis.Worker === "undefined") {
|
|
|
118
152
|
// and a parent→worker round-trip hangs — see wiki/research/worker-polyfill.md.
|
|
119
153
|
if (!isMainThread && parentPort) {
|
|
120
154
|
const scope = globalThis;
|
|
121
|
-
scope
|
|
155
|
+
// All of nub's worker-scope global injections below (self, addEventListener,
|
|
156
|
+
// removeEventListener, dispatchEvent, postMessage, close) are defined
|
|
157
|
+
// NON-ENUMERABLE. Node's worker global is not an EventTarget and exposes none
|
|
158
|
+
// of these, so a worker doing `Object.keys(globalThis)` / for-in must not see
|
|
159
|
+
// nub's additions — invisibility-to-enumeration is the additive contract.
|
|
160
|
+
// writable+configurable mirrors Node's own global descriptors. (`onmessage`/
|
|
161
|
+
// `onmessageerror` below already use Object.defineProperty, whose enumerable
|
|
162
|
+
// defaults to false.)
|
|
163
|
+
const defineGlobal = (name, value) =>
|
|
164
|
+
Object.defineProperty(scope, name, {
|
|
165
|
+
value,
|
|
166
|
+
enumerable: false,
|
|
167
|
+
writable: true,
|
|
168
|
+
configurable: true,
|
|
169
|
+
});
|
|
170
|
+
defineGlobal("self", scope);
|
|
122
171
|
|
|
123
172
|
// `message`/`messageerror` are DELEGATED straight onto the native `parentPort`
|
|
124
173
|
// (a real Node MessagePort) so Node's own C++ event-loop ref-counting governs
|
|
@@ -191,15 +240,15 @@ if (!isMainThread && parentPort) {
|
|
|
191
240
|
}
|
|
192
241
|
}
|
|
193
242
|
|
|
194
|
-
|
|
243
|
+
defineGlobal("addEventListener", (type, listener, opts) =>
|
|
195
244
|
DELEGATED.has(type)
|
|
196
245
|
? addDelegated(type, listener, opts)
|
|
197
|
-
: otherAdd(type, listener, opts);
|
|
198
|
-
|
|
246
|
+
: otherAdd(type, listener, opts));
|
|
247
|
+
defineGlobal("removeEventListener", (type, listener, opts) =>
|
|
199
248
|
DELEGATED.has(type)
|
|
200
249
|
? removeDelegated(type, listener)
|
|
201
|
-
: otherRemove(type, listener, opts);
|
|
202
|
-
|
|
250
|
+
: otherRemove(type, listener, opts));
|
|
251
|
+
defineGlobal("dispatchEvent", (ev) => otherDispatch(ev));
|
|
203
252
|
|
|
204
253
|
// `onmessage` / `onmessageerror` register via the delegating add/remove above,
|
|
205
254
|
// mirroring the web API and the main-side Worker. Assigning `null` removes the
|
|
@@ -221,9 +270,9 @@ if (!isMainThread && parentPort) {
|
|
|
221
270
|
|
|
222
271
|
// Outbound + lifecycle.
|
|
223
272
|
if (typeof scope.postMessage !== "function") {
|
|
224
|
-
|
|
273
|
+
defineGlobal("postMessage", (data, transfer) => parentPort.postMessage(data, transfer));
|
|
225
274
|
}
|
|
226
275
|
if (typeof scope.close !== "function") {
|
|
227
|
-
|
|
276
|
+
defineGlobal("close", () => process.exit(0));
|
|
228
277
|
}
|
|
229
278
|
}
|
package/runtime/polyfills.mjs
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
// Polyfill preloads for Nub v0.1. All feature-detect and bow out
|
|
2
|
-
// if the global is already present. Loaded via the main preload.
|
|
3
|
-
//
|
|
4
|
-
// Node 22.15+ (our floor) already has: navigator, navigator.locks,
|
|
5
|
-
// navigator.hardwareConcurrency, WebSocket. No polyfills needed.
|
|
6
|
-
//
|
|
7
|
-
// Node 24+ adds: URLPattern, RegExp.escape, Error.isError, Promise.try.
|
|
8
|
-
// We polyfill those on Node 22.x only.
|
|
9
|
-
//
|
|
10
|
-
// No Node version ships: Temporal, reportError, browser-shape Worker.
|
|
11
|
-
// These need polyfills on all supported versions.
|
|
12
|
-
|
|
13
|
-
// ── reportError (WinterTC min-common-API, not in any Node) ──────────
|
|
14
|
-
if (typeof globalThis.reportError !== "function") {
|
|
15
|
-
globalThis.reportError = (err) => {
|
|
16
|
-
queueMicrotask(() => {
|
|
17
|
-
throw err;
|
|
18
|
-
});
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ── URLPattern (native on Node 24+, missing on 22.x) ───────────────
|
|
23
|
-
if (typeof globalThis.URLPattern === "undefined") {
|
|
24
|
-
const mod = globalThis.__nubPreloaded?.urlpattern;
|
|
25
|
-
const URLPattern = mod?.URLPattern;
|
|
26
|
-
if (URLPattern) globalThis.URLPattern = URLPattern;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Temporal (in no Node version) is installed as a LAZY global by the main
|
|
30
|
-
// preload after this module runs — see preload.mjs (A37). Touching
|
|
31
|
-
// globalThis.Temporal here would defeat that laziness, so we must not.
|
|
32
|
-
|
|
33
|
-
// ── Stage 4 polyfills (native on Node 24+, missing on 22.x) ────────
|
|
34
|
-
|
|
35
|
-
// RegExp.escape — spec-faithful port of the TC39 proposal (native on Node 24+),
|
|
36
|
-
// so the 22.x floor behaves byte-for-byte like native: a leading digit/letter is
|
|
37
|
-
// control-escaped, syntax chars are backslashed, control chars use \t\n\v\f\r, and
|
|
38
|
-
// the "other punctuators" + whitespace set is hex-escaped. Verified byte-identical
|
|
39
|
-
// to Node's native RegExp.escape across every ASCII char + leading/whitespace/
|
|
40
|
-
// astral cases (so a concatenated `escape(s)` is safe too, not just
|
|
41
|
-
// `new RegExp(escape(s))`). The earlier reduced-fidelity version only escaped the
|
|
42
|
-
// syntax chars.
|
|
43
|
-
if (typeof RegExp.escape !== "function") {
|
|
44
|
-
const SYNTAX = new Set(["^", "$", "\\", ".", "*", "+", "?", "(", ")", "[", "]", "{", "}", "|", "/"]);
|
|
45
|
-
const CONTROL = { "\t": "\\t", "\n": "\\n", "\v": "\\v", "\f": "\\f", "\r": "\\r" };
|
|
46
|
-
// ASCII "other punctuators" the spec escapes by code, plus SPACE.
|
|
47
|
-
const OTHER = new Set([..." ,-=<>#&!%:;@~'\"`"]);
|
|
48
|
-
const isWhiteSpace = (cp) =>
|
|
49
|
-
cp === 0x09 || cp === 0x0a || cp === 0x0b || cp === 0x0c || cp === 0x0d ||
|
|
50
|
-
cp === 0x20 || cp === 0xa0 || cp === 0x1680 || (cp >= 0x2000 && cp <= 0x200a) ||
|
|
51
|
-
cp === 0x2028 || cp === 0x2029 || cp === 0x202f || cp === 0x205f || cp === 0x3000 ||
|
|
52
|
-
cp === 0xfeff;
|
|
53
|
-
const hexEscape = (cp) => {
|
|
54
|
-
if (cp <= 0xff) return "\\x" + cp.toString(16).padStart(2, "0");
|
|
55
|
-
if (cp <= 0xffff) return "\\u" + cp.toString(16).padStart(4, "0");
|
|
56
|
-
const h = cp - 0x10000;
|
|
57
|
-
const hi = 0xd800 + (h >> 10);
|
|
58
|
-
const lo = 0xdc00 + (h & 0x3ff);
|
|
59
|
-
return "\\u" + hi.toString(16).padStart(4, "0") + "\\u" + lo.toString(16).padStart(4, "0");
|
|
60
|
-
};
|
|
61
|
-
const encode = (ch, cp) =>
|
|
62
|
-
SYNTAX.has(ch)
|
|
63
|
-
? "\\" + ch
|
|
64
|
-
: CONTROL[ch] ?? ((OTHER.has(ch) || isWhiteSpace(cp)) ? hexEscape(cp) : ch);
|
|
65
|
-
RegExp.escape = (s) => {
|
|
66
|
-
if (typeof s !== "string") throw new TypeError("RegExp.escape argument must be a string");
|
|
67
|
-
const cps = [...s]; // iterate by code point (astral-safe)
|
|
68
|
-
let out = "";
|
|
69
|
-
for (let i = 0; i < cps.length; i++) {
|
|
70
|
-
const ch = cps[i];
|
|
71
|
-
const cp = ch.codePointAt(0);
|
|
72
|
-
// A leading decimal-digit/ASCII-letter is control-escaped so a preceding `\`
|
|
73
|
-
// in a concatenated pattern can't form an escape sequence.
|
|
74
|
-
if (i === 0 && ((cp >= 0x30 && cp <= 0x39) || (cp >= 0x41 && cp <= 0x5a) || (cp >= 0x61 && cp <= 0x7a))) {
|
|
75
|
-
out += "\\x" + cp.toString(16).padStart(2, "0");
|
|
76
|
-
} else {
|
|
77
|
-
out += encode(ch, cp);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return out;
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Error.isError (~95% fidelity — cross-realm internal-slot unreachable)
|
|
85
|
-
if (typeof Error.isError !== "function") {
|
|
86
|
-
Error.isError = (value) => {
|
|
87
|
-
if (value == null || typeof value !== "object") return false;
|
|
88
|
-
return value instanceof Error;
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Promise.try
|
|
93
|
-
if (typeof Promise.try !== "function") {
|
|
94
|
-
Promise.try = (fn, ...args) => {
|
|
95
|
-
return new Promise((resolve) => resolve(fn(...args)));
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Float16Array (TC39 Stage 4, native on Node 24+; absent on our 22.x floor).
|
|
100
|
-
// Installed from the spec-compliant @petamoriken/float16 polyfill (vendored,
|
|
101
|
-
// preloaded by preload.mjs). It provides the full TypedArray method surface
|
|
102
|
-
// (map/filter/subarray/set/reduce/…) and correct round-to-nearest-even,
|
|
103
|
-
// including subnormals — unlike the prior hand-rolled Proxy shim, which had
|
|
104
|
-
// ~30 methods missing and truncating/denormal-flushing conversion.
|
|
105
|
-
//
|
|
106
|
-
// INHERENT userland limitation (not fixable by any JS polyfill): a polyfilled
|
|
107
|
-
// Float16Array isn't recognized by `ArrayBuffer.isView()` (it has no V8 internal
|
|
108
|
-
// [[TypedArrayName]] slot). Code needing that check should use the polyfill's
|
|
109
|
-
// `isFloat16Array`. See wiki/runtime/float16array-polyfill.md.
|
|
110
|
-
if (typeof globalThis.Float16Array === "undefined") {
|
|
111
|
-
const f16 = globalThis.__nubPreloaded?.float16;
|
|
112
|
-
if (f16?.Float16Array) {
|
|
113
|
-
globalThis.Float16Array = f16.Float16Array;
|
|
114
|
-
|
|
115
|
-
if (typeof DataView.prototype.getFloat16 !== "function") {
|
|
116
|
-
DataView.prototype.getFloat16 = function (offset, littleEndian) {
|
|
117
|
-
return f16.getFloat16(this, offset, littleEndian);
|
|
118
|
-
};
|
|
119
|
-
DataView.prototype.setFloat16 = function (offset, value, littleEndian) {
|
|
120
|
-
f16.setFloat16(this, offset, value, littleEndian);
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (typeof Math.f16round !== "function") {
|
|
125
|
-
Math.f16round = f16.f16round;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ── navigator.locks (native on Node 24+, missing on 22.x) ──────────
|
|
131
|
-
if (typeof globalThis.navigator?.locks === "undefined") {
|
|
132
|
-
await import("./navigator-locks.mjs");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ── Worker (browser-shape global, not in any Node) ──────────────────
|
|
136
|
-
await import("./worker-polyfill.mjs");
|