@nubjs/nub-linux-x64 0.2.1 → 0.2.3

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.
@@ -28,8 +28,8 @@
28
28
  // loader worker, not a user realm, so it installs no browser globals.)
29
29
  import "./floor-builtin.mjs";
30
30
  import {
31
- TRANSPILE_EXTS, DATA_EXTS,
32
- extname, resolveSpec, loadTranspile, loadData,
31
+ TRANSPILE_EXTS, PLAIN_JS_EXTS, DATA_EXTS,
32
+ extname, resolveSpec, loadTranspile, maybeTranspilePlainJs, loadData, isNodeModules,
33
33
  } from "./transform-core.mjs";
34
34
  import { createRequire, isBuiltin } from "node:module";
35
35
  import { existsSync } from "node:fs";
@@ -85,13 +85,26 @@ export async function resolve(specifier, context, nextResolve) {
85
85
  // ── Load hook ───────────────────────────────────────────────────────
86
86
  export async function load(url, context, nextLoad) {
87
87
  const ext = extname(url);
88
- if (TRANSPILE_EXTS.has(ext)) {
88
+ // node_modules deps are NEVER transpiled (the byte-parity boundary). This guard is
89
+ // make-or-break now that TRANSPILE_EXTS includes `.js`/`.mjs`/`.cjs`: without it,
90
+ // the compat tier would route every dependency `.js` through oxc. (loadTranspile's
91
+ // own skip-gate handles the project-source no-op case; this keeps deps off the
92
+ // pipeline entirely.) Mirrors the fast-tier sync hook's `!isNodeModules` gate.
93
+ if (TRANSPILE_EXTS.has(ext) && !isNodeModules(url)) {
89
94
  // Module-format + decorator detection inside loadTranspile is a synchronous
90
95
  // native call (nub's addon), available on every supported Node — no parser
91
96
  // warm-up needed (the old `await ensureParser()` for the ESM-only oxc-parser
92
97
  // is gone with the package).
93
98
  return loadTranspile(url, ext);
94
99
  }
100
+ // Project-source plain JS: transpile ONLY when it carries transformable syntax. A
101
+ // no-op plain-JS file returns null and falls through to `nextLoad` — Node's own
102
+ // loader handles it byte-identically, preserving every native CJS/ESM behavior.
103
+ // node_modules excluded (the byte-parity boundary).
104
+ if (PLAIN_JS_EXTS.has(ext) && !isNodeModules(url)) {
105
+ const r = maybeTranspilePlainJs(url, ext);
106
+ if (r) return r;
107
+ }
95
108
  if (ext in DATA_EXTS) return loadData(url, ext);
96
109
  return nextLoad(url, context);
97
110
  }
@@ -373,6 +373,13 @@ function makeHooks(core, watchReporting) {
373
373
  if (core.TRANSPILE_EXTS.has(ext) && !core.isNodeModules(url)) {
374
374
  return core.loadTranspile(url, ext);
375
375
  }
376
+ // Plain JS: transpile only when transformable; else fall through to the raw-
377
+ // source path below (which hands CJS back as `source:null` → Node's native
378
+ // CJS loader), byte-identical to a non-intercepted file.
379
+ if (core.PLAIN_JS_EXTS.has(ext) && !core.isNodeModules(url)) {
380
+ const r = core.maybeTranspilePlainJs(url, ext);
381
+ if (r) return r;
382
+ }
376
383
  if (ext in core.DATA_EXTS) return core.loadData(url, ext);
377
384
  const { readFileSync } = require("node:fs");
378
385
  const source = readFileSync(path);
@@ -432,6 +439,15 @@ function makeHooks(core, watchReporting) {
432
439
  if (core.TRANSPILE_EXTS.has(ext) && !core.isNodeModules(url)) {
433
440
  return core.loadTranspile(url, ext);
434
441
  }
442
+ // Project-source plain JS (`.js`/`.mjs`/`.cjs`): transpile ONLY when it carries
443
+ // transformable syntax. A no-op plain-JS file returns null here and falls through
444
+ // to Node's native loader (the `nextLoad`/relabel path below) BYTE-FOR-BYTE — it
445
+ // is never intercepted, so native CJS/ESM behavior (the relabel, require.cache,
446
+ // the require-of-ESM-syntax-`.cjs` error) is preserved. node_modules excluded.
447
+ if (core.PLAIN_JS_EXTS.has(ext) && !core.isNodeModules(url)) {
448
+ const r = core.maybeTranspilePlainJs(url, ext);
449
+ if (r) return r;
450
+ }
435
451
  if (ext in core.DATA_EXTS) return core.loadData(url, ext);
436
452
 
437
453
  // Fidelity: a `data:` URL whose MIME maps to no module format (e.g.
@@ -673,6 +689,39 @@ function installCjsRequireHooks(core, withClassicTranspile) {
673
689
  for (const ext of [".ts", ".cts", ".mts", ".tsx", ".jsx"]) {
674
690
  module_._extensions[ext] = transpileExtension;
675
691
  }
692
+
693
+ // Project-source plain JS (`.js`/`.cjs`) routes through the SAME pipeline so
694
+ // `using`/`v`-flag-RegExp/decorators lower uniformly on the classic require tier —
695
+ // but ONLY when the file carries transformable syntax. THREE cases, each preserving
696
+ // Node's native behavior where it must:
697
+ // (1) node_modules dep → native handler (deps are NEVER transpiled). Node has no
698
+ // own `_extensions['.cjs']` (only `.js`/`.json`/`.node`), and compiles `.cjs`
699
+ // through the same CJS path as `.js`, so the `.cjs` bail falls back to the
700
+ // `.js` handler (`|| nativeJs`) — without it a node_modules `.cjs` require
701
+ // would call `undefined` and crash.
702
+ // (2) project file with transformable syntax → transpile (lower it).
703
+ // (3) project file with NOTHING to lower → the ORIGINAL native handler, raw bytes
704
+ // compiled exactly as Node would. We never serve our own source for a no-op
705
+ // file, so require.cache / the require-of-ESM-syntax-`.cjs` SyntaxError / every
706
+ // native CJS behavior is byte-identical.
707
+ // `.mjs` is ESM-only; Node registers no require.extensions handler for it, so a
708
+ // `require()` of `.mjs` throws ERR_REQUIRE_ESM as before — we don't override it.
709
+ const nativeJs = module_._extensions[".js"];
710
+ for (const ext of [".js", ".cjs"]) {
711
+ const origExtension = module_._extensions[ext] || nativeJs;
712
+ module_._extensions[ext] = (mod, filename) => {
713
+ if (core.isNodeModules(pathToFileURL(filename).href)) {
714
+ return origExtension.call(module_._extensions, mod, filename); // (1)
715
+ }
716
+ const r = core.maybeTranspilePlainJs(pathToFileURL(filename).href, pathExtname(filename));
717
+ if (r) {
718
+ if (r.format === "module") throw requireEsmError(filename); // (2)
719
+ mod._compile(r.source, filename);
720
+ return;
721
+ }
722
+ return origExtension.call(module_._extensions, mod, filename); // (3)
723
+ };
724
+ }
676
725
  }
677
726
 
678
727
  // ── Clobbered-polyfill preloading + Temporal lazy global ────────────
@@ -209,6 +209,21 @@ function installLazyEsmPolyfills() {
209
209
  }
210
210
  };
211
211
 
212
+ // navigator backfill, ordered BEFORE the navigator.locks installs below so locks
213
+ // always has a host. The fast tier floor is 22.15 (navigator is always native, >= 21),
214
+ // so installNavigatorShim() version-checks and returns WITHOUT reading globalThis
215
+ // .navigator — never triggering the 24.5+ lazy-navigator realization. It does real
216
+ // work only if this preload is ever reached on Node < 21; on the fast floor it is a
217
+ // cheap no-op, wired for symmetry with the compat tier. See navigator-shim.mjs.
218
+ try {
219
+ __require("./navigator-shim.mjs").installNavigatorShim();
220
+ } catch (err) {
221
+ // require(esm) disabled (--no-experimental-require-module): navigator is native at
222
+ // the fast floor, so skipping the no-op shim is harmless. Any OTHER error is a real
223
+ // fault in the module — surface it rather than swallow it.
224
+ if (!err || err.code !== "ERR_REQUIRE_ESM") throw err;
225
+ }
226
+
212
227
  if (inWorkerThread) {
213
228
  // Worker-side scope bootstrap must be present synchronously where possible.
214
229
  loadEsmSideEffect("./worker-polyfill.mjs");
@@ -112,6 +112,14 @@ if (__isFastTier) {
112
112
  // modules is unreliable, so they load via dynamic `import()` here.
113
113
  const __preloadedPolyfills = common.preloadPolyfillPackages(__require);
114
114
  installSyncPolyfills(__preloadedPolyfills);
115
+ // navigator backfill (Node < 21: the `navigator` global is wholly absent). MUST run
116
+ // BEFORE navigator-locks so Web Locks has a host on the 18.19–20.x floor. On Node >= 21
117
+ // it is a no-op (navigator is native). Thread the floor's createRequire so the shim can
118
+ // fetch node:os where process.getBuiltinModule is absent (the narrow floor). See
119
+ // navigator-shim.mjs.
120
+ const __navShim = await import("./navigator-shim.mjs");
121
+ __navShim.setBootstrapCreateRequire(floorCreateRequire);
122
+ __navShim.installNavigatorShim();
115
123
  if (typeof globalThis.navigator?.locks === "undefined") {
116
124
  await import("./navigator-locks.mjs");
117
125
  }
@@ -156,7 +156,21 @@ if (typeof process.getBuiltinModule === "function") __ensureBuiltins();
156
156
  // natively now, and this file no longer needs to read version.mjs.
157
157
 
158
158
  // ── Constants ───────────────────────────────────────────────────────
159
+ // TS/JSX exts ALWAYS transform (type-stripping is required), so they live in
160
+ // TRANSPILE_EXTS — the set every dispatch site checks to route a file to
161
+ // loadTranspile. Plain JS (.js/.mjs/.cjs) is DELIBERATELY NOT here: a plain-JS file
162
+ // is transpiled ONLY when it carries transformable syntax (`using`/`await using`,
163
+ // `v`-flag RegExp, decorators), and a no-op plain-JS file must take Node's OWN load
164
+ // path BYTE-FOR-BYTE — putting it in TRANSPILE_EXTS would route every `.js`/`.cjs`
165
+ // through nub's hook and change native CJS/ESM behavior (the `commonjs-sync` relabel,
166
+ // require.cache, the require-of-ESM-syntax-.cjs error). So plain JS is handled by a
167
+ // SEPARATE narrow path (`maybeTranspilePlainJs`) that fires only for transformable
168
+ // files and is a no-op (returns null) otherwise — see PLAIN_JS_EXTS below.
159
169
  export const TRANSPILE_EXTS = new Set([".ts", ".tsx", ".mts", ".cts", ".jsx"]);
170
+ // Project-source plain JS. Routed to the transpiler ONLY when transformable (the
171
+ // `maybeTranspilePlainJs` gate); a no-op plain-JS file falls through to Node's
172
+ // native loader untouched, byte-identical. node_modules is excluded at the gate.
173
+ export const PLAIN_JS_EXTS = new Set([".js", ".mjs", ".cjs"]);
160
174
  export const DATA_EXTS = { ".jsonc": "jsonc", ".json5": "json5", ".toml": "toml", ".yaml": "yaml", ".yml": "yaml", ".txt": "txt" };
161
175
  export const TS_PARENT_EXTS = new Set([".ts", ".tsx", ".mts", ".cts"]);
162
176
 
@@ -328,7 +342,7 @@ export function resolveSpec(specifier, parentURL) {
328
342
  // from a nub-dependency ESM entry) delegated to a strict user loader is exactly
329
343
  // the R11 leak. See isNubInternalParent.
330
344
  if (isNubInternalParent(parentURL)) {
331
- if (specifier.startsWith("node:") || module.builtinModules.includes(specifier)) {
345
+ if (specifier.startsWith("node:") || module.isBuiltin(specifier)) {
332
346
  const url = specifier.startsWith("node:") ? specifier : `node:${specifier}`;
333
347
  return { url, shortCircuit: true };
334
348
  }
@@ -349,7 +363,7 @@ export function resolveSpec(specifier, parentURL) {
349
363
 
350
364
  // node: and data: protocols, and bare Node built-ins, are never ours.
351
365
  if (specifier.startsWith("node:") || specifier.startsWith("data:")) return null;
352
- if (module.builtinModules.includes(specifier)) return null;
366
+ if (module.isBuiltin(specifier)) return null;
353
367
 
354
368
  // 1. Built-in modules provided by Nub.
355
369
  if (BUILTIN_MODULES.has(specifier)) {
@@ -392,7 +406,7 @@ export function resolveSpec(specifier, parentURL) {
392
406
  // file's absolute path (from the CJS parent Module), or null for the entry.
393
407
  export function resolveCjsPath(request, parentPath) {
394
408
  if (request.startsWith("node:") || request.startsWith("data:") ||
395
- module.builtinModules.includes(request)) {
409
+ module.isBuiltin(request)) {
396
410
  return null;
397
411
  }
398
412
  // The SAME native additive resolver as resolveSpec, returning an absolute path
@@ -435,13 +449,15 @@ function detectModuleInfo(filePath, source, lang) {
435
449
  // Addon missing (should never happen in a real install): default to ESM for
436
450
  // format (the common case) and "no decorators" for the guard — the same fallback
437
451
  // the old oxc-parser-unavailable branches used.
438
- if (!nubNative) return { hasValueEsmSyntax: true, hasDecorators: false };
452
+ if (!nubNative) return { hasValueEsmSyntax: true, hasDecorators: false, transformableSyntax: false };
439
453
  try {
440
454
  return nubNative.detectModuleInfo(filePath, source, lang);
441
455
  } catch {
442
456
  // Unparseable → CJS for format + no decorators (the transpile/V8 surfaces the
443
- // real error), matching the old per-call catch blocks.
444
- return { hasValueEsmSyntax: false, hasDecorators: false };
457
+ // real error), matching the old per-call catch blocks. `transformableSyntax:
458
+ // false` is the SAFE plain-JS default — the verbatim path hands the raw bytes
459
+ // back, so V8 surfaces the real syntax error exactly where Node would.
460
+ return { hasValueEsmSyntax: false, hasDecorators: false, transformableSyntax: false };
445
461
  }
446
462
  }
447
463
 
@@ -450,13 +466,28 @@ function detectModuleInfo(filePath, source, lang) {
450
466
  // `type` is authoritative; otherwise (ambiguous) we detect from source syntax —
451
467
  // full Node parity (`--experimental-detect-module`), so a CJS-syntax `.ts` with
452
468
  // no `type` runs as CJS on nub exactly as on Node. See wiki/runtime/module-format.md.
469
+ // `.mjs`→module / `.cjs`→commonjs are explicit (mirroring `.mts`/`.cts`), so the
470
+ // plain-JS gate gets the right format without a needless detect.
453
471
  export function moduleFormatFor(ext, pkgType, filePath, source) {
454
- if (ext === ".mts") return "module";
455
- if (ext === ".cts") return "commonjs";
456
- if (pkgType === "module") return "module";
457
- if (pkgType === "commonjs") return "commonjs";
472
+ return moduleFormatWithInfo(ext, pkgType, filePath, source).format;
473
+ }
474
+
475
+ // Same format decision as moduleFormatFor, but ALSO returns the `ModuleInfo`
476
+ // (`detectModuleInfo`) result when a parse was needed — `{ format, info }`, with
477
+ // `info` null on the no-parse short-circuits (`.mts`/`.mjs`/`.cts`/`.cjs`, explicit
478
+ // `type`). loadTranspile uses this so its ONE parse serves BOTH readers — the
479
+ // format decision (`hasValueEsmSyntax`) and the Stage-3 decorator guard
480
+ // (`hasDecorators`) — instead of `moduleFormatFor` + `hasDecoratorSyntax` each
481
+ // parsing the same source. On a short-circuit (`info` null) no parse happened, so
482
+ // the decorator guard runs its own single parse: still ≤1 detect per file.
483
+ function moduleFormatWithInfo(ext, pkgType, filePath, source) {
484
+ if (ext === ".mts" || ext === ".mjs") return { format: "module", info: null };
485
+ if (ext === ".cts" || ext === ".cjs") return { format: "commonjs", info: null };
486
+ if (pkgType === "module") return { format: "module", info: null };
487
+ if (pkgType === "commonjs") return { format: "commonjs", info: null };
458
488
  const lang = ext === ".tsx" ? "tsx" : ext === ".jsx" ? "jsx" : "ts";
459
- return detectModuleInfo(filePath, source, lang).hasValueEsmSyntax ? "module" : "commonjs";
489
+ const info = detectModuleInfo(filePath, source, lang);
490
+ return { format: info.hasValueEsmSyntax ? "module" : "commonjs", info };
460
491
  }
461
492
 
462
493
  // The Stage-3-decorator rejection diagnostic. oxc does not lower TC39 Stage 3
@@ -568,9 +599,13 @@ export function loadTranspile(url, ext) {
568
599
  // (.ts/.tsx/.jsx); .mts/.cts are explicit so its lookup is skipped. The chosen
569
600
  // format is folded into the cache key (and the entry's leading byte) by native.
570
601
  const pkgType = ext === ".mts" || ext === ".cts" ? undefined : getPackageType(dir);
571
- const format = moduleFormatFor(ext, pkgType, filePath, source);
602
+ // ONE detectModuleInfo parse for both the format decision and the decorator
603
+ // guard below: `moduleInfo` is the parsed ModuleInfo when the format needed a
604
+ // parse (ambiguous ext, no explicit `type`), else null (a no-parse short-circuit).
605
+ const { format, info: moduleInfo } = moduleFormatWithInfo(ext, pkgType, filePath, source);
572
606
 
573
607
  const lang = ext === ".tsx" ? "tsx" : ext === ".jsx" ? "jsx" : "ts";
608
+
574
609
  const opts = {
575
610
  lang,
576
611
  sourceType: format === "commonjs" ? "commonjs" : "module",
@@ -604,9 +639,12 @@ export function loadTranspile(url, ext) {
604
639
  // SyntaxError. When legacy mode is off and decorator syntax is present, reject
605
640
  // with the documented Option-A diagnostic instead. (Cheap `source.includes("@")`
606
641
  // pre-filter keeps decorator-free files off the native parser; runs BEFORE the
607
- // cache so the diagnostic surfaces even on what would be a warm hit.)
642
+ // cache so the diagnostic surfaces even on what would be a warm hit.) Reuse the
643
+ // `hasDecorators` flag from the format parse above when it ran (`moduleInfo`
644
+ // non-null), so the ambiguous-ext + `@` path detects ONCE; on a no-parse
645
+ // short-circuit (`.mts`/`.cts`/explicit `type`) it does its own single parse.
608
646
  if (co?.experimentalDecorators !== true && source.includes("@") &&
609
- hasDecoratorSyntax(filePath, source, lang)) {
647
+ (moduleInfo ? moduleInfo.hasDecorators : hasDecoratorSyntax(filePath, source, lang))) {
610
648
  throw stage3DecoratorError(filePath);
611
649
  }
612
650
 
@@ -626,6 +664,40 @@ export function loadTranspile(url, ext) {
626
664
  return { format: result.format, source: result.code, shortCircuit: true };
627
665
  }
628
666
 
667
+ // Project-source plain JS (`.js`/`.mjs`/`.cjs`) gate. Returns a transpiled load
668
+ // result ONLY when the file carries syntax oxc lowers at nub's es2022 target
669
+ // (`using`/`await using`, a `v`-flag RegExp, or decorators); otherwise returns
670
+ // `null`, meaning "this file needs no transform — handle it with Node's OWN loader,
671
+ // exactly as a non-listed extension." This is why `.js`/`.mjs`/`.cjs` are NOT in
672
+ // TRANSPILE_EXTS: a no-op plain-JS file must take Node's native load path
673
+ // byte-for-byte (preserving the `commonjs-sync` relabel, require.cache, the
674
+ // require-of-ESM-syntax-`.cjs` error — all of which intercepting the file would
675
+ // break), and oxc would reformat it (quotes/semicolons/whitespace + a sourcemap
676
+ // footer) if we ran it through anyway. The verdict rides ONE parse (the same one
677
+ // `detectModuleInfo` does for format detection). node_modules is gated at the call
678
+ // sites (the byte-parity boundary). JSX-in-`.js` is out of scope (lang is "ts",
679
+ // which does not parse JSX); use `.jsx`.
680
+ export function maybeTranspilePlainJs(url, ext) {
681
+ __ensureBuiltins();
682
+ const filePath = fileURLToPath(url);
683
+ let source;
684
+ try {
685
+ source = readFileSync(filePath, "utf8");
686
+ } catch {
687
+ // Unreadable here → let Node's loader surface its own error.
688
+ return null;
689
+ }
690
+ // lang "ts" parses all JS (a TS superset) but NOT JSX — JSX-in-.js is out of scope.
691
+ const info = detectModuleInfo(filePath, source, "ts");
692
+ if (!info.transformableSyntax && !info.hasDecorators) {
693
+ return null; // no-op: Node's native loader handles it, byte-identical.
694
+ }
695
+ // Transformable: run the SAME pipeline as TS/JSX (target es2022 lowering, tsconfig,
696
+ // source maps, the Stage-3 decorator guard, format detection, cache). loadTranspile
697
+ // re-reads + re-parses, but only for the rare file that actually needs lowering.
698
+ return loadTranspile(url, ext);
699
+ }
700
+
629
701
  // ── Data-format imports ─────────────────────────────────────────────
630
702
  function lazyRequire(pkg) {
631
703
  try { return __require(pkg); } catch {
@@ -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.2.1";
12
+ export const NUB_VERSION = "0.2.3";