@nubjs/loader 0.9.0 → 0.9.1
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 +9 -9
- package/preload-async-hooks.mjs +6 -1
- package/preload-common.cjs +146 -9
- package/transform-core.mjs +26 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubjs/loader",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
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.
|
|
53
|
-
"@nubjs/loader-darwin-x64": "0.9.
|
|
54
|
-
"@nubjs/loader-linux-x64": "0.9.
|
|
55
|
-
"@nubjs/loader-linux-x64-musl": "0.9.
|
|
56
|
-
"@nubjs/loader-linux-arm64": "0.9.
|
|
57
|
-
"@nubjs/loader-linux-arm64-musl": "0.9.
|
|
58
|
-
"@nubjs/loader-win32-x64": "0.9.
|
|
59
|
-
"@nubjs/loader-win32-arm64": "0.9.
|
|
52
|
+
"@nubjs/loader-darwin-arm64": "0.9.1",
|
|
53
|
+
"@nubjs/loader-darwin-x64": "0.9.1",
|
|
54
|
+
"@nubjs/loader-linux-x64": "0.9.1",
|
|
55
|
+
"@nubjs/loader-linux-x64-musl": "0.9.1",
|
|
56
|
+
"@nubjs/loader-linux-arm64": "0.9.1",
|
|
57
|
+
"@nubjs/loader-linux-arm64-musl": "0.9.1",
|
|
58
|
+
"@nubjs/loader-win32-x64": "0.9.1",
|
|
59
|
+
"@nubjs/loader-win32-arm64": "0.9.1"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/preload-async-hooks.mjs
CHANGED
|
@@ -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,
|
|
33
|
+
noteRuntimeV8FlagSource, outerHookOwnsFormat,
|
|
34
34
|
} from "./transform-core.mjs";
|
|
35
35
|
import { createRequire, isBuiltin } from "node:module";
|
|
36
36
|
import { existsSync } from "node:fs";
|
|
@@ -114,6 +114,11 @@ 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);
|
|
117
122
|
// node_modules deps are NEVER transpiled (the byte-parity boundary). This guard is
|
|
118
123
|
// make-or-break now that TRANSPILE_EXTS includes `.js`/`.mjs`/`.cjs`: without it,
|
|
119
124
|
// the compat tier would route every dependency `.js` through oxc. (loadTranspile's
|
package/preload-common.cjs
CHANGED
|
@@ -231,6 +231,10 @@ 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;
|
|
234
238
|
function installUserHookDetector() {
|
|
235
239
|
if (typeof module_.registerHooks !== "function") return;
|
|
236
240
|
const orig = module_.registerHooks;
|
|
@@ -238,7 +242,10 @@ function installUserHookDetector() {
|
|
|
238
242
|
let seen = 0;
|
|
239
243
|
const wrapped = function (...args) {
|
|
240
244
|
// Call #1 is nub's own preload registration; #2+ are user hooks.
|
|
241
|
-
if (seen >= 1)
|
|
245
|
+
if (seen >= 1) {
|
|
246
|
+
__userHooksRegistered = true;
|
|
247
|
+
if (args[0] && typeof args[0].load === "function") __userLoadHookRegistered = true;
|
|
248
|
+
}
|
|
242
249
|
seen += 1;
|
|
243
250
|
return orig.apply(this, args);
|
|
244
251
|
};
|
|
@@ -698,8 +705,8 @@ function makeHooks(core, watchReporting, foreignLoaderFlagPresent = foreignAsync
|
|
|
698
705
|
const source = readFileSync(path);
|
|
699
706
|
const pkgType = core.getPackageType(dirname(path));
|
|
700
707
|
const format = core.moduleFormatFor(ext, pkgType, path, source.toString("utf8"));
|
|
701
|
-
if (format === "commonjs") return { format: "commonjs", source: null, shortCircuit: true };
|
|
702
|
-
return { format, source, shortCircuit: true };
|
|
708
|
+
if (format === "commonjs") return { format: "commonjs", source: null, responseURL: url, shortCircuit: true };
|
|
709
|
+
return { format, source, responseURL: url, shortCircuit: true };
|
|
703
710
|
} catch {
|
|
704
711
|
return null;
|
|
705
712
|
}
|
|
@@ -773,7 +780,22 @@ function makeHooks(core, watchReporting, foreignLoaderFlagPresent = foreignAsync
|
|
|
773
780
|
// user's outer hook, which does the real ESM->CJS conversion, matching Node.
|
|
774
781
|
// Native 'module-typescript'/'commonjs-typescript' formats still fall through to
|
|
775
782
|
// nub's transpile below, so normal augmentation is unchanged.
|
|
776
|
-
|
|
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
|
+
)) {
|
|
777
799
|
return nextLoad(url, context);
|
|
778
800
|
}
|
|
779
801
|
|
|
@@ -1027,10 +1049,36 @@ function installCjsRequireHooks(core, withClassicTranspile) {
|
|
|
1027
1049
|
// missing. An explicit `require("dep/x/sub.cjs")` is unaffected either way — an
|
|
1028
1050
|
// exact path is found by stat, without consulting the extension list at all.
|
|
1029
1051
|
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]);
|
|
1030
1078
|
const withoutNubAddedExtensions = (fn) => {
|
|
1031
1079
|
const saved = [];
|
|
1032
1080
|
for (const ext of NUB_ADDED_EXTS) {
|
|
1033
|
-
if (
|
|
1081
|
+
if (nubOwnsExt(ext)) {
|
|
1034
1082
|
saved.push([ext, module_._extensions[ext]]);
|
|
1035
1083
|
delete module_._extensions[ext];
|
|
1036
1084
|
}
|
|
@@ -1043,7 +1091,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
|
|
|
1043
1091
|
};
|
|
1044
1092
|
const isDepAddedExtHit = (filename) =>
|
|
1045
1093
|
typeof filename === "string" &&
|
|
1046
|
-
|
|
1094
|
+
nubOwnsExt(pathExtname(filename)) &&
|
|
1047
1095
|
core.isDependency(pathToFileURL(filename).href);
|
|
1048
1096
|
|
|
1049
1097
|
module_._resolveFilename = function (request, parent, isMain, options) {
|
|
@@ -1066,7 +1114,8 @@ function installCjsRequireHooks(core, withClassicTranspile) {
|
|
|
1066
1114
|
// and 4 from `require.resolve` up to 22.14, but 22.15 and 22.16 pass 4 for both
|
|
1067
1115
|
// while still lacking native TS — and the translator crash is present on
|
|
1068
1116
|
// exactly those versions. A clean error beats an opaque crash, so this stays.
|
|
1069
|
-
if (withClassicTranspile &&
|
|
1117
|
+
if (withClassicTranspile && nubOwnsExt(pathExtname(resolved)) &&
|
|
1118
|
+
core.requireTargetIsEsm(resolved, pathExtname(resolved))) {
|
|
1070
1119
|
throw requireEsmError(resolved);
|
|
1071
1120
|
}
|
|
1072
1121
|
return resolved;
|
|
@@ -1195,6 +1244,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
|
|
|
1195
1244
|
if (core.TRANSPILE_EXTS.has(ext)) return transpileExtension(mod, filename);
|
|
1196
1245
|
return nativeJs.call(module_._extensions, mod, filename);
|
|
1197
1246
|
};
|
|
1247
|
+
nubHandlers.add(nubExtension);
|
|
1198
1248
|
|
|
1199
1249
|
// Registered for every extension either path may claim, so the dispatcher is
|
|
1200
1250
|
// reached at all; `nubExtension` then decides. Node's own `.js`/`.json`/`.node`
|
|
@@ -1215,7 +1265,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
|
|
|
1215
1265
|
// resolution identical across tiers.
|
|
1216
1266
|
const CODE_EXTS = new Set([".ts", ".cts", ".mts", ".tsx", ".jsx"]);
|
|
1217
1267
|
for (const ext of new Set([...core.TRANSPILE_EXTS, ...core.allDataExts()])) {
|
|
1218
|
-
if (ext === ".js" || ext === ".json" || ext === ".node") continue;
|
|
1268
|
+
if (ext === ".js" || ext === ".json" || ext === ".node" || foreignExts.has(ext)) continue;
|
|
1219
1269
|
Object.defineProperty(module_._extensions, ext, {
|
|
1220
1270
|
value: nubExtension,
|
|
1221
1271
|
enumerable: CODE_EXTS.has(ext),
|
|
@@ -1243,7 +1293,7 @@ function installCjsRequireHooks(core, withClassicTranspile) {
|
|
|
1243
1293
|
// (`nativeJs` is captured above, before the TS handlers are registered.)
|
|
1244
1294
|
for (const ext of [".js", ".cjs"]) {
|
|
1245
1295
|
const origExtension = module_._extensions[ext] || nativeJs;
|
|
1246
|
-
|
|
1296
|
+
const plainJsExtension = (mod, filename) => {
|
|
1247
1297
|
// (0) The project pointed this extension at a data loader (`{".js":"text"}`),
|
|
1248
1298
|
// which the ESM path honors. These two extensions are skipped by the
|
|
1249
1299
|
// registration loop above because THIS wrapper owns them and runs after it,
|
|
@@ -1266,6 +1316,8 @@ function installCjsRequireHooks(core, withClassicTranspile) {
|
|
|
1266
1316
|
}
|
|
1267
1317
|
return origExtension.call(module_._extensions, mod, filename); // (3)
|
|
1268
1318
|
};
|
|
1319
|
+
module_._extensions[ext] = plainJsExtension;
|
|
1320
|
+
nubHandlers.add(plainJsExtension);
|
|
1269
1321
|
}
|
|
1270
1322
|
}
|
|
1271
1323
|
|
|
@@ -1705,6 +1757,90 @@ function installVersionMarker() {
|
|
|
1705
1757
|
} catch {}
|
|
1706
1758
|
}
|
|
1707
1759
|
|
|
1760
|
+
// ── libuv threadpool policy ──────────────────────────────────────────
|
|
1761
|
+
// The launcher sized the pool (`UV_THREADPOOL_SIZE = max(4, cores)`, spawn.rs) and
|
|
1762
|
+
// Node read it at startup, so the variable has done its work for THIS process.
|
|
1763
|
+
// Two things remain, both about the cores the extra threads would take:
|
|
1764
|
+
//
|
|
1765
|
+
// 1. Children keep Node's default. A `cluster` or PM2 fork, or any `child_process`
|
|
1766
|
+
// spawn, inherits `process.env`; N workers each carrying a cores-sized pool is
|
|
1767
|
+
// the oversubscription Node's own maintainers closed nodejs/node#61533 over.
|
|
1768
|
+
// So nub's OWN value is deleted from `process.env`. Ownership is two markers
|
|
1769
|
+
// the launcher stamps (spawn.rs `RestorableVar`): the value equals the
|
|
1770
|
+
// `__NUB_AUGMENTED_*` record of what the launcher handed Node, AND the compat
|
|
1771
|
+
// presence mask says the variable was ABSENT before the outermost nub ran (bit
|
|
1772
|
+
// 1 << 5 is this variable's slot). A shell value, an env-file value, or a value
|
|
1773
|
+
// the launcher merely passed through fails one of the two and inherits as it
|
|
1774
|
+
// would under plain Node. The markers themselves stay, which is how a nested
|
|
1775
|
+
// `nub` knows to size its child again. libuv reads the variable LAZILY, at the
|
|
1776
|
+
// first pool use, and a `process.env` delete reaches the C environment, so the
|
|
1777
|
+
// pool is created (one `fs.access`) before the variable goes; otherwise libuv
|
|
1778
|
+
// would find nothing and build Node's four. A pool of four is Node's default,
|
|
1779
|
+
// so the variable goes at once and nothing is demoted.
|
|
1780
|
+
// 2. The threads beyond Node's four run at a lower priority on Linux, so they only
|
|
1781
|
+
// take cycles nothing else on the box wants (Chromium's best-effort tier, nice
|
|
1782
|
+
// 10). Measured on 16 vCPU beside twelve busy processes: the neighbours keep
|
|
1783
|
+
// 98.6% of their CPU instead of 92.5%, the server still gains 20% over four
|
|
1784
|
+
// threads, and an idle box loses nothing. libuv creates every worker
|
|
1785
|
+
// synchronously inside the first pool submit, so one `fs.access` call makes
|
|
1786
|
+
// them all exist (`access` never takes the io_uring path that lets stat, read
|
|
1787
|
+
// and open skip the pool); the new thread ids (or the `libuv-worker` name) name
|
|
1788
|
+
// them, and `os.setPriority(tid)` targets one thread on Linux. The thread ids
|
|
1789
|
+
// are exact only across the call that builds the pool: the fast tier's
|
|
1790
|
+
// `--require` preload runs before any pool use and builds it here, but the
|
|
1791
|
+
// compat tier's `--import` preload is itself read through the pool, so the
|
|
1792
|
+
// launcher `--require`s threadpool-snapshot.cjs ahead of it to build the pool
|
|
1793
|
+
// and record its threads there.
|
|
1794
|
+
const THREADPOOL_ENV = "UV_THREADPOOL_SIZE";
|
|
1795
|
+
const THREADPOOL_MARK_ENV = "__NUB_AUGMENTED_UV_THREADPOOL_SIZE";
|
|
1796
|
+
const COMPAT_PRESENT_ENV = "__NUB_COMPAT_PRESENT";
|
|
1797
|
+
const THREADPOOL_PRESENT_BIT = 1 << 5;
|
|
1798
|
+
const THREADPOOL_NODE_DEFAULT = 4;
|
|
1799
|
+
const THREADPOOL_EXTRA_NICE = 10;
|
|
1800
|
+
const THREADPOOL_WORKERS = Symbol.for("nub.threadpool.workers");
|
|
1801
|
+
|
|
1802
|
+
function installThreadpoolPolicy() {
|
|
1803
|
+
const size = process.env[THREADPOOL_ENV];
|
|
1804
|
+
if (size === undefined || size !== process.env[THREADPOOL_MARK_ENV]) return;
|
|
1805
|
+
if ((Number(process.env[COMPAT_PRESENT_ENV]) || 0) & THREADPOOL_PRESENT_BIT) return;
|
|
1806
|
+
if (!(Number(size) > THREADPOOL_NODE_DEFAULT)) {
|
|
1807
|
+
// Node's own size: nothing to demote, and libuv builds the same four whether
|
|
1808
|
+
// it still finds the variable or not, so it goes at once.
|
|
1809
|
+
delete process.env[THREADPOOL_ENV];
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
// The `--require` preload re-runs inside every loader worker; the pool is
|
|
1814
|
+
// process-wide, so only the main thread touches it.
|
|
1815
|
+
if (!require("node:worker_threads").isMainThread) return;
|
|
1816
|
+
const fs = require("node:fs");
|
|
1817
|
+
const linux = process.platform === "linux";
|
|
1818
|
+
const tids = () => fs.readdirSync("/proc/self/task").map(Number).filter(Boolean);
|
|
1819
|
+
let workers = linux ? process[THREADPOOL_WORKERS] : undefined;
|
|
1820
|
+
if (workers === undefined) {
|
|
1821
|
+
const before = linux ? new Set(tids()) : null;
|
|
1822
|
+
fs.access("/", () => {});
|
|
1823
|
+
const isWorker = (t) => {
|
|
1824
|
+
if (!before.has(t)) return true;
|
|
1825
|
+
try {
|
|
1826
|
+
return fs.readFileSync(`/proc/self/task/${t}/comm`, "latin1").trim() === "libuv-worker";
|
|
1827
|
+
} catch {
|
|
1828
|
+
return false;
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
if (linux) workers = tids().filter(isWorker);
|
|
1832
|
+
}
|
|
1833
|
+
delete process.env[THREADPOOL_ENV];
|
|
1834
|
+
if (!linux) return;
|
|
1835
|
+
const os = require("node:os");
|
|
1836
|
+
for (const t of workers.sort((a, b) => a - b).slice(THREADPOOL_NODE_DEFAULT)) {
|
|
1837
|
+
try {
|
|
1838
|
+
os.setPriority(t, THREADPOOL_EXTRA_NICE);
|
|
1839
|
+
} catch {}
|
|
1840
|
+
}
|
|
1841
|
+
} catch {}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1708
1844
|
// ── User preloads (`nub.jsonc` `preload`) ───────────────────────────
|
|
1709
1845
|
// nub loads the user's preload entries HERE rather than emitting one NODE_OPTIONS
|
|
1710
1846
|
// token per entry. Two reasons, and the first is a correctness bug in the wild:
|
|
@@ -1756,6 +1892,7 @@ async function importUserPreloadChain() {
|
|
|
1756
1892
|
|
|
1757
1893
|
module.exports = {
|
|
1758
1894
|
installVersionMarker,
|
|
1895
|
+
installThreadpoolPolicy,
|
|
1759
1896
|
installWatchReporting,
|
|
1760
1897
|
registerLoaderWorker,
|
|
1761
1898
|
makeHooks,
|
package/transform-core.mjs
CHANGED
|
@@ -207,6 +207,20 @@ 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
|
+
}
|
|
210
224
|
// The data loaders nub SHIPS — a runtime feature, not a project setting, so they stay
|
|
211
225
|
// in force inside node_modules too (see dataExtsFor).
|
|
212
226
|
const BUILTIN_DATA_EXTS = { ".jsonc": "jsonc", ".json5": "json5", ".toml": "toml", ".yaml": "yaml", ".yml": "yaml", ".txt": "txt" };
|
|
@@ -1007,7 +1021,15 @@ export function loadTranspile(url, ext, source) {
|
|
|
1007
1021
|
const details = result.errors.map((e) => e.codeframe || e.message).join("\n\n");
|
|
1008
1022
|
throw new Error(`Transpile error in ${filePath}:\n${details}`);
|
|
1009
1023
|
}
|
|
1010
|
-
|
|
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 };
|
|
1011
1033
|
}
|
|
1012
1034
|
|
|
1013
1035
|
// Project-source plain JS (`.js`/`.mjs`/`.cjs`) gate. Returns a transpiled load
|
|
@@ -1126,7 +1148,7 @@ export function loadData(url, ext) {
|
|
|
1126
1148
|
const parsed = dataValue(url, ext);
|
|
1127
1149
|
|
|
1128
1150
|
if (parsed == null) {
|
|
1129
|
-
return { format: "module", source: "export default undefined;\n", shortCircuit: true };
|
|
1151
|
+
return { format: "module", source: "export default undefined;\n", responseURL: url, shortCircuit: true };
|
|
1130
1152
|
}
|
|
1131
1153
|
|
|
1132
1154
|
// Default export only. Data modules deliberately do NOT emit per-key named
|
|
@@ -1136,7 +1158,7 @@ export function loadData(url, ext) {
|
|
|
1136
1158
|
// default — `import cfg from "./c.yaml"; const { host } = cfg;` — which the
|
|
1137
1159
|
// `@nubjs/types` `Record<string, unknown>` default type makes sound.
|
|
1138
1160
|
const code = `export default ${JSON.stringify(parsed)};\n`;
|
|
1139
|
-
return { format: "module", source: code, shortCircuit: true };
|
|
1161
|
+
return { format: "module", source: code, responseURL: url, shortCircuit: true };
|
|
1140
1162
|
}
|
|
1141
1163
|
|
|
1142
1164
|
// Import Text: `import s from "./any.file" with { type: "text" }` → the raw file
|
|
@@ -1152,5 +1174,5 @@ export function loadData(url, ext) {
|
|
|
1152
1174
|
const __textDecoder = new TextDecoder();
|
|
1153
1175
|
export function loadTextImport(url) {
|
|
1154
1176
|
const text = __textDecoder.decode(readFileSync(fileURLToPath(url)));
|
|
1155
|
-
return { format: "module", source: `export default ${JSON.stringify(text)};\n`, shortCircuit: true };
|
|
1177
|
+
return { format: "module", source: `export default ${JSON.stringify(text)};\n`, responseURL: url, shortCircuit: true };
|
|
1156
1178
|
}
|