@nubjs/loader 0.9.0-canary.20260908.508 → 0.9.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubjs/loader",
3
- "version": "0.9.0-canary.20260908.508",
3
+ "version": "0.9.0",
4
4
  "description": "Standalone TypeScript loader for Node.js from the Nub project — TypeScript, JSX, tsconfig paths, and data-format imports through a native transform, registered the way tsx and ts-node are",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/nubjs/nub",
@@ -49,13 +49,13 @@
49
49
  "@oxc-project/runtime": "0.140.0"
50
50
  },
51
51
  "optionalDependencies": {
52
- "@nubjs/loader-darwin-arm64": "0.9.0-canary.20260908.508",
53
- "@nubjs/loader-darwin-x64": "0.9.0-canary.20260908.508",
54
- "@nubjs/loader-linux-x64": "0.9.0-canary.20260908.508",
55
- "@nubjs/loader-linux-x64-musl": "0.9.0-canary.20260908.508",
56
- "@nubjs/loader-linux-arm64": "0.9.0-canary.20260908.508",
57
- "@nubjs/loader-linux-arm64-musl": "0.9.0-canary.20260908.508",
58
- "@nubjs/loader-win32-x64": "0.9.0-canary.20260908.508",
59
- "@nubjs/loader-win32-arm64": "0.9.0-canary.20260908.508"
52
+ "@nubjs/loader-darwin-arm64": "0.9.0",
53
+ "@nubjs/loader-darwin-x64": "0.9.0",
54
+ "@nubjs/loader-linux-x64": "0.9.0",
55
+ "@nubjs/loader-linux-x64-musl": "0.9.0",
56
+ "@nubjs/loader-linux-arm64": "0.9.0",
57
+ "@nubjs/loader-linux-arm64-musl": "0.9.0",
58
+ "@nubjs/loader-win32-x64": "0.9.0",
59
+ "@nubjs/loader-win32-arm64": "0.9.0"
60
60
  }
61
61
  }
@@ -30,7 +30,7 @@ import "./floor-builtin.mjs";
30
30
  import {
31
31
  TRANSPILE_EXTS, PLAIN_JS_EXTS, CLOBBER_MAP, dataExtsFor,
32
32
  extname, isFileUrl, resolveSpec, loadTranspile, maybeTranspilePlainJs, loadData, loadTextImport, isDependency,
33
- noteRuntimeV8FlagSource, outerHookOwnsFormat,
33
+ noteRuntimeV8FlagSource,
34
34
  } from "./transform-core.mjs";
35
35
  import { createRequire, isBuiltin } from "node:module";
36
36
  import { existsSync } from "node:fs";
@@ -114,11 +114,6 @@ async function loadInner(url, context, nextLoad) {
114
114
  // fast-tier hook.
115
115
  if (context?.importAttributes?.type === "text" && isFileUrl(url)) return loadTextImport(url);
116
116
  const ext = extname(url);
117
- // A user loader above nub's assigned this TypeScript file a bare module format
118
- // and will transform it itself from the raw source (tsx's pattern; see
119
- // `outerHookOwnsFormat`). This tier has no registration counter like the fast
120
- // tier's and needs none: nothing but such a hook produces the bare form here.
121
- if (outerHookOwnsFormat(context?.format, ext) && !isDependency(url)) return nextLoad(url, context);
122
117
  // node_modules deps are NEVER transpiled (the byte-parity boundary). This guard is
123
118
  // make-or-break now that TRANSPILE_EXTS includes `.js`/`.mjs`/`.cjs`: without it,
124
119
  // the compat tier would route every dependency `.js` through oxc. (loadTranspile's
@@ -231,10 +231,6 @@ function installWatchReporting(core) {
231
231
  // `type` (transpile — there is no user hook to do it, and Node's strip-only mode
232
232
  // can't handle enums/namespaces). See makeHooks().load.
233
233
  let __userHooksRegistered = false;
234
- // Narrower: a user registration that brought a `load` hook, so it can transform
235
- // what nub hands back. A resolve-only user hook that labels a `.ts` file with a
236
- // bare `commonjs`/`module` still relies on nub as the transformer.
237
- let __userLoadHookRegistered = false;
238
234
  function installUserHookDetector() {
239
235
  if (typeof module_.registerHooks !== "function") return;
240
236
  const orig = module_.registerHooks;
@@ -242,10 +238,7 @@ function installUserHookDetector() {
242
238
  let seen = 0;
243
239
  const wrapped = function (...args) {
244
240
  // Call #1 is nub's own preload registration; #2+ are user hooks.
245
- if (seen >= 1) {
246
- __userHooksRegistered = true;
247
- if (args[0] && typeof args[0].load === "function") __userLoadHookRegistered = true;
248
- }
241
+ if (seen >= 1) __userHooksRegistered = true;
249
242
  seen += 1;
250
243
  return orig.apply(this, args);
251
244
  };
@@ -705,8 +698,8 @@ function makeHooks(core, watchReporting, foreignLoaderFlagPresent = foreignAsync
705
698
  const source = readFileSync(path);
706
699
  const pkgType = core.getPackageType(dirname(path));
707
700
  const format = core.moduleFormatFor(ext, pkgType, path, source.toString("utf8"));
708
- if (format === "commonjs") return { format: "commonjs", source: null, responseURL: url, shortCircuit: true };
709
- return { format, source, responseURL: url, shortCircuit: true };
701
+ if (format === "commonjs") return { format: "commonjs", source: null, shortCircuit: true };
702
+ return { format, source, shortCircuit: true };
710
703
  } catch {
711
704
  return null;
712
705
  }
@@ -780,22 +773,7 @@ function makeHooks(core, watchReporting, foreignLoaderFlagPresent = foreignAsync
780
773
  // user's outer hook, which does the real ESM->CJS conversion, matching Node.
781
774
  // Native 'module-typescript'/'commonjs-typescript' formats still fall through to
782
775
  // nub's transpile below, so normal augmentation is unchanged.
783
- //
784
- // tsx 4.2x writes the bare `commonjs`/`module` form instead of 'typescript'
785
- // (`outerHookOwnsFormat`), and the hook that wrote it must also be able to
786
- // transform what it gets back: a user registration WITH a load hook
787
- // (`__userLoadHookRegistered`), or a loader registered through
788
- // `module.register` (`userAsyncLoaderActive`), which the registerHooks counter
789
- // cannot see and which nub has no way to inspect. A resolve-only user hook
790
- // that labels a `.ts` file keeps nub as its transformer. The 'typescript'
791
- // branch keeps its own narrower gate: a non-transpiling async loader (a
792
- // telemetry `--import`) must not turn a bare 'typescript' from Node's own
793
- // CJS loader into a step-aside.
794
- if (context && (
795
- (__userHooksRegistered && context.format === "typescript") ||
796
- ((__userLoadHookRegistered || userAsyncLoaderActive(foreignLoaderFlagPresent)) &&
797
- core.outerHookOwnsFormat(context.format, ext) && !core.isDependency(url))
798
- )) {
776
+ if (__userHooksRegistered && context && context.format === "typescript") {
799
777
  return nextLoad(url, context);
800
778
  }
801
779
 
@@ -1049,36 +1027,10 @@ function installCjsRequireHooks(core, withClassicTranspile) {
1049
1027
  // missing. An explicit `require("dep/x/sub.cjs")` is unaffected either way — an
1050
1028
  // exact path is found by stat, without consulting the extension list at all.
1051
1029
  const NUB_ADDED_EXTS = [".ts", ".cts", ".mts", ".tsx", ".jsx", ".cjs"];
1052
- // A `require.extensions` handler that is ALREADY registered for one of these
1053
- // when this runs belongs to a transpiler the user chose, and it stays in charge
1054
- // of that extension: nub's classic shim neither replaces it nor pre-judges its
1055
- // files as ES modules at resolve time. Every `--require` runs before any `--import`, so on
1056
- // the compat tier — where nub's own preload is an `--import` — tsx's
1057
- // `--require`d preflight installs its `.ts` handler FIRST; nub then overwrote
1058
- // it, and a file tsx would have compiled to CJS (mixed `import` + `require`,
1059
- // the shape its ESM hook hands to the CJS loader) died in nub's handler as
1060
- // "Cannot require() this file — it is an ES module". Node itself registers only
1061
- // `.js`/`.json`/`.node`, so nothing but a user transpiler holds one of these
1062
- // keys here.
1063
- const foreignExts = new Set(
1064
- [...core.TRANSPILE_EXTS, ...core.allDataExts(), ...NUB_ADDED_EXTS].filter((ext) =>
1065
- ext !== ".js" && ext !== ".json" && ext !== ".node" && Object.hasOwn(module_._extensions, ext)),
1066
- );
1067
- // Ownership of a key is decided per lookup, never frozen at start-up: user code
1068
- // may install or replace a handler after nub's preload (`--import tsx` runs
1069
- // after every `--require`), and from then on that key is the user's own
1070
- // widening of LOAD_AS_FILE, exactly as under plain Node plus their handler —
1071
- // `require("dep/sub")` finding a dependency's `sub.ts` through it is the answer
1072
- // to preserve. A key is nub's only while BOTH hold: nub introduced it (it was
1073
- // not in `foreignExts` — a user's `.cjs` handler that nub merely wraps still
1074
- // counts as theirs) and it still holds a function nub registered below
1075
- // (`nubHandlers`). Anything else is not nub's to strip, nor to pre-judge.
1076
- const nubHandlers = new Set();
1077
- const nubOwnsExt = (ext) => !foreignExts.has(ext) && nubHandlers.has(module_._extensions[ext]);
1078
1030
  const withoutNubAddedExtensions = (fn) => {
1079
1031
  const saved = [];
1080
1032
  for (const ext of NUB_ADDED_EXTS) {
1081
- if (nubOwnsExt(ext)) {
1033
+ if (Object.hasOwn(module_._extensions, ext)) {
1082
1034
  saved.push([ext, module_._extensions[ext]]);
1083
1035
  delete module_._extensions[ext];
1084
1036
  }
@@ -1091,7 +1043,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
1091
1043
  };
1092
1044
  const isDepAddedExtHit = (filename) =>
1093
1045
  typeof filename === "string" &&
1094
- nubOwnsExt(pathExtname(filename)) &&
1046
+ NUB_ADDED_EXTS.includes(pathExtname(filename)) &&
1095
1047
  core.isDependency(pathToFileURL(filename).href);
1096
1048
 
1097
1049
  module_._resolveFilename = function (request, parent, isMain, options) {
@@ -1114,8 +1066,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
1114
1066
  // and 4 from `require.resolve` up to 22.14, but 22.15 and 22.16 pass 4 for both
1115
1067
  // while still lacking native TS — and the translator crash is present on
1116
1068
  // exactly those versions. A clean error beats an opaque crash, so this stays.
1117
- if (withClassicTranspile && nubOwnsExt(pathExtname(resolved)) &&
1118
- core.requireTargetIsEsm(resolved, pathExtname(resolved))) {
1069
+ if (withClassicTranspile && core.requireTargetIsEsm(resolved, pathExtname(resolved))) {
1119
1070
  throw requireEsmError(resolved);
1120
1071
  }
1121
1072
  return resolved;
@@ -1244,7 +1195,6 @@ function installCjsRequireHooks(core, withClassicTranspile) {
1244
1195
  if (core.TRANSPILE_EXTS.has(ext)) return transpileExtension(mod, filename);
1245
1196
  return nativeJs.call(module_._extensions, mod, filename);
1246
1197
  };
1247
- nubHandlers.add(nubExtension);
1248
1198
 
1249
1199
  // Registered for every extension either path may claim, so the dispatcher is
1250
1200
  // reached at all; `nubExtension` then decides. Node's own `.js`/`.json`/`.node`
@@ -1265,7 +1215,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
1265
1215
  // resolution identical across tiers.
1266
1216
  const CODE_EXTS = new Set([".ts", ".cts", ".mts", ".tsx", ".jsx"]);
1267
1217
  for (const ext of new Set([...core.TRANSPILE_EXTS, ...core.allDataExts()])) {
1268
- if (ext === ".js" || ext === ".json" || ext === ".node" || foreignExts.has(ext)) continue;
1218
+ if (ext === ".js" || ext === ".json" || ext === ".node") continue;
1269
1219
  Object.defineProperty(module_._extensions, ext, {
1270
1220
  value: nubExtension,
1271
1221
  enumerable: CODE_EXTS.has(ext),
@@ -1293,7 +1243,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
1293
1243
  // (`nativeJs` is captured above, before the TS handlers are registered.)
1294
1244
  for (const ext of [".js", ".cjs"]) {
1295
1245
  const origExtension = module_._extensions[ext] || nativeJs;
1296
- const plainJsExtension = (mod, filename) => {
1246
+ module_._extensions[ext] = (mod, filename) => {
1297
1247
  // (0) The project pointed this extension at a data loader (`{".js":"text"}`),
1298
1248
  // which the ESM path honors. These two extensions are skipped by the
1299
1249
  // registration loop above because THIS wrapper owns them and runs after it,
@@ -1316,8 +1266,6 @@ function installCjsRequireHooks(core, withClassicTranspile) {
1316
1266
  }
1317
1267
  return origExtension.call(module_._extensions, mod, filename); // (3)
1318
1268
  };
1319
- module_._extensions[ext] = plainJsExtension;
1320
- nubHandlers.add(plainJsExtension);
1321
1269
  }
1322
1270
  }
1323
1271
 
@@ -207,20 +207,6 @@ export const TRANSPILE_EXTS = new Set([".ts", ".tsx", ".mts", ".cts", ".jsx"]);
207
207
  // `maybeTranspilePlainJs` gate); a no-op plain-JS file falls through to Node's
208
208
  // native loader untouched, byte-identical. node_modules is excluded at the gate.
209
209
  export const PLAIN_JS_EXTS = new Set([".js", ".mjs", ".cjs"]);
210
- // A bare `commonjs`/`module` format on a file nub would transpile can only have
211
- // been assigned by a hook layered ABOVE nub's. Node's resolver labels a `.ts` file
212
- // `commonjs-typescript`/`module-typescript` (or `typescript` when the package has
213
- // no `type`) and leaves `.jsx` unlabelled, and nub's own resolve returns no format;
214
- // the bare form is what tsx's resolve hook writes (getFormatFromFileUrl), and its
215
- // load hook then expects the RAW source back from `nextLoad` so it can run its own
216
- // module-format transform — a mixed `import` + `require` file becomes CJS there,
217
- // where nub's syntax detection would make it ESM and `require` undefined. Both
218
- // tiers step aside on this signal, the fast tier only once the user registration
219
- // is known to carry a load hook (preload-common.cjs). Plain JS is excluded: Node
220
- // assigns those the bare form itself.
221
- export function outerHookOwnsFormat(format, ext) {
222
- return (format === "commonjs" || format === "module") && TRANSPILE_EXTS.has(ext) && !PLAIN_JS_EXTS.has(ext);
223
- }
224
210
  // The data loaders nub SHIPS — a runtime feature, not a project setting, so they stay
225
211
  // in force inside node_modules too (see dataExtsFor).
226
212
  const BUILTIN_DATA_EXTS = { ".jsonc": "jsonc", ".json5": "json5", ".toml": "toml", ".yaml": "yaml", ".yml": "yaml", ".txt": "txt" };
@@ -1021,15 +1007,7 @@ export function loadTranspile(url, ext, source) {
1021
1007
  const details = result.errors.map((e) => e.codeframe || e.message).join("\n\n");
1022
1008
  throw new Error(`Transpile error in ${filePath}:\n${details}`);
1023
1009
  }
1024
- // `responseURL` is what an OUTER user hook keys on. Node's default load sets it
1025
- // to the file URL, and tsx's load hook takes its CommonJS branch only when the
1026
- // result `nextLoad` hands back carries a `file:` responseURL — without it, tsx
1027
- // re-transformed a `.ts` file nub had already emitted as CJS in its ESM branch,
1028
- // and `require` was undefined at run time (`tsx script.ts` under `nub run`).
1029
- // Node itself defaults a missing responseURL to the URL, so only a hook layered
1030
- // above nub's can observe the difference; every file-URL result nub
1031
- // short-circuits carries it for that reason.
1032
- return { format: result.format, source: result.code, responseURL: url, shortCircuit: true };
1010
+ return { format: result.format, source: result.code, shortCircuit: true };
1033
1011
  }
1034
1012
 
1035
1013
  // Project-source plain JS (`.js`/`.mjs`/`.cjs`) gate. Returns a transpiled load
@@ -1148,7 +1126,7 @@ export function loadData(url, ext) {
1148
1126
  const parsed = dataValue(url, ext);
1149
1127
 
1150
1128
  if (parsed == null) {
1151
- return { format: "module", source: "export default undefined;\n", responseURL: url, shortCircuit: true };
1129
+ return { format: "module", source: "export default undefined;\n", shortCircuit: true };
1152
1130
  }
1153
1131
 
1154
1132
  // Default export only. Data modules deliberately do NOT emit per-key named
@@ -1158,7 +1136,7 @@ export function loadData(url, ext) {
1158
1136
  // default — `import cfg from "./c.yaml"; const { host } = cfg;` — which the
1159
1137
  // `@nubjs/types` `Record<string, unknown>` default type makes sound.
1160
1138
  const code = `export default ${JSON.stringify(parsed)};\n`;
1161
- return { format: "module", source: code, responseURL: url, shortCircuit: true };
1139
+ return { format: "module", source: code, shortCircuit: true };
1162
1140
  }
1163
1141
 
1164
1142
  // Import Text: `import s from "./any.file" with { type: "text" }` → the raw file
@@ -1174,5 +1152,5 @@ export function loadData(url, ext) {
1174
1152
  const __textDecoder = new TextDecoder();
1175
1153
  export function loadTextImport(url) {
1176
1154
  const text = __textDecoder.decode(readFileSync(fileURLToPath(url)));
1177
- return { format: "module", source: `export default ${JSON.stringify(text)};\n`, responseURL: url, shortCircuit: true };
1155
+ return { format: "module", source: `export default ${JSON.stringify(text)};\n`, shortCircuit: true };
1178
1156
  }