mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
feat(compiler): add Node module resolution introspection (#322)
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.15.0",
|
||||
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
|
||||
"artifactVersion": "66b252bc9bee6f91f667",
|
||||
"artifactVersion": "039dcd2c69e09f0e38f2",
|
||||
"rowCount": 3662
|
||||
}
|
||||
|
||||
@@ -602,18 +602,18 @@
|
||||
"apiEntries": 27,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 13,
|
||||
"partial": 15,
|
||||
"refused": 0,
|
||||
"not-implemented": 11,
|
||||
"not-implemented": 9,
|
||||
"by-design": 0,
|
||||
"unreviewed": 3,
|
||||
"not-applicable": 0
|
||||
},
|
||||
"dynamic": {
|
||||
"supported": 0,
|
||||
"partial": 18,
|
||||
"partial": 20,
|
||||
"refused": 0,
|
||||
"not-implemented": 9,
|
||||
"not-implemented": 7,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -626,18 +626,18 @@
|
||||
"apiEntries": 22,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 11,
|
||||
"partial": 12,
|
||||
"refused": 0,
|
||||
"not-implemented": 11,
|
||||
"not-implemented": 10,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
},
|
||||
"dynamic": {
|
||||
"supported": 0,
|
||||
"partial": 8,
|
||||
"partial": 9,
|
||||
"refused": 0,
|
||||
"not-implemented": 14,
|
||||
"not-implemented": 13,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -56489,14 +56489,14 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "Not implemented in scriptc's static module-loader subset yet.",
|
||||
"verification": "declared-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"detail": "Not implemented in the embedded module-loader subset yet.",
|
||||
"verification": "declared-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented embedded module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -56517,14 +56517,14 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "Not implemented in scriptc's static module-loader subset yet.",
|
||||
"verification": "declared-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"detail": "Not implemented in the embedded module-loader subset yet.",
|
||||
"verification": "declared-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented embedded module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -57105,14 +57105,14 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "Not implemented in scriptc's static module-loader subset yet.",
|
||||
"verification": "declared-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"detail": "Not implemented in the embedded module-loader subset yet.",
|
||||
"verification": "declared-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented embedded module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
{ "chapter": "environment_variables", "symbols": ["process.env"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
{ "chapter": "environment_variables", "symbols": ["export"], "status": "not-applicable" },
|
||||
{ "chapter": "modules", "symbols": ["__dirname", "__filename", "exports", "module", "require", "cache", "filename", "id", "loaded", "parent", "path"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/create-require/main.ts", "tests/fixtures/npm/cases/dynamic-import/main.ts"] },
|
||||
{ "chapter": "modules", "symbols": ["require.resolve", "require.resolve.paths", "extensions", "main", "module.require", "children", "isPreloading", "paths", "Module"], "status": "not-implemented" },
|
||||
{ "chapter": "modules", "symbols": ["require.resolve", "require.resolve.paths"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/create-require/main.ts"] },
|
||||
{ "chapter": "modules", "symbols": ["extensions", "main", "module.require", "children", "isPreloading", "paths", "Module"], "status": "not-implemented" },
|
||||
{ "chapter": "modules", "symbols": ["node:", "node_modules"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/builtin-shims/main.ts", "tests/fixtures/npm/cases/create-require/main.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["import", "file:", "node:", "await", "url"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dynamic-import/main.ts", "tests/corpus/2646-top-level-await.ts"] },
|
||||
{ "chapter": "esm", "signatures": ["`import()` expressions"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dynamic-import/main.ts", "tests/corpus/2050-dynamic-import-own-module/main.ts", "tests/corpus/2790-dynamic-import-self-name/main.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["meta", "import.meta.resolve", "dirname", "filename", "main", "data:", "require", "__filename", "require.main", "require.resolve", "NODE_PATH", "require.extensions", "require.cache"], "status": "not-implemented" },
|
||||
{ "chapter": "esm", "symbols": ["import.meta.resolve"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dual-entry/main.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["meta", "dirname", "filename", "main", "data:", "require", "__filename", "require.main", "require.resolve", "NODE_PATH", "require.extensions", "require.cache"], "status": "not-implemented" },
|
||||
{ "chapter": "packages", "symbols": ["package.json", "\"name\"", "\"main\"", "\"type\"", "\"exports\"", "\"imports\""], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dual-entry/main.ts", "tests/fixtures/npm/cases/self-name/main.ts", "tests/fixtures/npm/cases/scoped-nested/main.ts", "tests/corpus/2790-dynamic-import-self-name/main.ts"] },
|
||||
{ "chapter": "packages", "symbols": ["--input-type"], "status": "not-applicable" },
|
||||
{ "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2705-type-import-link-dynamic/main.ts", "tests/corpus/2706-type-import-link-dynamic-success/main.ts", "tests/corpus/2708-type-import-link-dynamic/main.ts", "tests/harness/errors.test.ts"] }
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
"static": {
|
||||
"replace-refusal": 73,
|
||||
"verify-gap": 2759,
|
||||
"audit-partial": 376,
|
||||
"audit-partial": 379,
|
||||
"classify": 3,
|
||||
"implement": 92
|
||||
"implement": 89
|
||||
},
|
||||
"dynamic": {
|
||||
"audit-partial": 745,
|
||||
"audit-partial": 748,
|
||||
"verify-gap": 2302,
|
||||
"replace-refusal": 101,
|
||||
"implement": 220
|
||||
"implement": 217
|
||||
}
|
||||
},
|
||||
"priorities": {
|
||||
@@ -59564,20 +59564,25 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "implement",
|
||||
"verification": "declared-gap",
|
||||
"confidence": "medium",
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:modules.require.resolve",
|
||||
"tests": []
|
||||
"tests": [
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs",
|
||||
"tests/corpus/2860-require-resolve/main.cjs"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"action": "implement",
|
||||
"verification": "declared-gap",
|
||||
"confidence": "medium",
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "island-feature:modules.require.resolve",
|
||||
"tests": []
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/create-require/main.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -59597,20 +59602,25 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "implement",
|
||||
"verification": "declared-gap",
|
||||
"confidence": "medium",
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:modules.require.resolve.paths",
|
||||
"tests": []
|
||||
"tests": [
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs",
|
||||
"tests/corpus/2860-require-resolve/main.cjs"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"action": "implement",
|
||||
"verification": "declared-gap",
|
||||
"confidence": "medium",
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "island-feature:modules.require.resolve.paths",
|
||||
"tests": []
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/create-require/main.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -60321,20 +60331,24 @@
|
||||
"priority": "normal",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "implement",
|
||||
"verification": "declared-gap",
|
||||
"confidence": "medium",
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:esm.import.meta.resolve",
|
||||
"tests": []
|
||||
"tests": [
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"action": "implement",
|
||||
"verification": "declared-gap",
|
||||
"confidence": "medium",
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "island-feature:esm.import.meta.resolve",
|
||||
"tests": []
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/dual-entry/main.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -675,18 +675,18 @@
|
||||
"apiEntries": 27,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 13,
|
||||
"partial": 15,
|
||||
"refused": 0,
|
||||
"not-implemented": 11,
|
||||
"not-implemented": 9,
|
||||
"by-design": 0,
|
||||
"unreviewed": 3,
|
||||
"not-applicable": 0
|
||||
},
|
||||
"dynamic": {
|
||||
"supported": 0,
|
||||
"partial": 18,
|
||||
"partial": 20,
|
||||
"refused": 0,
|
||||
"not-implemented": 9,
|
||||
"not-implemented": 7,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -699,18 +699,18 @@
|
||||
"apiEntries": 22,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 11,
|
||||
"partial": 12,
|
||||
"refused": 0,
|
||||
"not-implemented": 11,
|
||||
"not-implemented": 10,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
},
|
||||
"dynamic": {
|
||||
"supported": 0,
|
||||
"partial": 8,
|
||||
"partial": 9,
|
||||
"refused": 0,
|
||||
"not-implemented": 14,
|
||||
"not-implemented": 13,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -73697,7 +73697,9 @@
|
||||
"tests/corpus/1598-cjs-builtin-require/main.js",
|
||||
"tests/corpus/1612-cjs-module-globals.cjs",
|
||||
"tests/corpus/1629-require-main-filename.cjs",
|
||||
"tests/corpus/2830-cjs-require-self-name/main.cjs"
|
||||
"tests/corpus/2830-cjs-require-self-name/main.cjs",
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs",
|
||||
"tests/corpus/2860-require-resolve/main.cjs"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -73971,12 +73973,19 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-feature:modules.require.resolve"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:modules.require.resolve",
|
||||
"tests": [
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs",
|
||||
"tests/corpus/2860-require-resolve/main.cjs"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "island-feature:modules.require.resolve"
|
||||
"status": "partial",
|
||||
"evidence": "island-feature:modules.require.resolve",
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/create-require/main.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -73998,12 +74007,19 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-feature:modules.require.resolve.paths"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:modules.require.resolve.paths",
|
||||
"tests": [
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs",
|
||||
"tests/corpus/2860-require-resolve/main.cjs"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "island-feature:modules.require.resolve.paths"
|
||||
"status": "partial",
|
||||
"evidence": "island-feature:modules.require.resolve.paths",
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/create-require/main.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -74947,6 +74963,7 @@
|
||||
"tests": [
|
||||
"tests/corpus/2646-top-level-await.ts",
|
||||
"tests/corpus/2701-import-meta/main.mjs",
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs",
|
||||
"tests/corpus/950-modules-basic/main.ts"
|
||||
]
|
||||
},
|
||||
@@ -74955,6 +74972,7 @@
|
||||
"evidence": "derived:descendants",
|
||||
"tests": [
|
||||
"tests/corpus/2646-top-level-await.ts",
|
||||
"tests/fixtures/npm/cases/dual-entry/main.ts",
|
||||
"tests/fixtures/npm/cases/dynamic-import/main.ts"
|
||||
]
|
||||
},
|
||||
@@ -75005,12 +75023,18 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-feature:esm.import.meta.resolve"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:esm.import.meta.resolve",
|
||||
"tests": [
|
||||
"tests/corpus/2858-import-meta-resolve/main.mjs"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "island-feature:esm.import.meta.resolve"
|
||||
"status": "partial",
|
||||
"evidence": "island-feature:esm.import.meta.resolve",
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/dual-entry/main.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
|
||||
@@ -75,11 +75,13 @@
|
||||
{ "chapter": "environment_variables", "symbols": ["export"], "status": "not-applicable" },
|
||||
{ "chapter": "globals", "symbols": ["fetch"], "status": "partial", "evidence": ["tests/harness/fetch-conformance.test.ts"] },
|
||||
{ "chapter": "modules", "symbols": ["__dirname", "__filename", "exports", "require", "module.require", "main", "filename"], "status": "partial", "evidence": ["tests/corpus/1612-cjs-module-globals.cjs", "tests/corpus/1596-cjs-modules/main.js", "tests/corpus/1629-require-main-filename.cjs", "tests/corpus/2830-cjs-require-self-name/main.cjs"] },
|
||||
{ "chapter": "modules", "symbols": ["require.resolve", "require.resolve.paths", "cache", "extensions", "children", "id", "isPreloading", "loaded", "parent", "paths", "Module"], "status": "not-implemented" },
|
||||
{ "chapter": "modules", "symbols": ["require.resolve", "require.resolve.paths"], "status": "partial", "evidence": ["tests/corpus/2858-import-meta-resolve/main.mjs", "tests/corpus/2860-require-resolve/main.cjs"] },
|
||||
{ "chapter": "modules", "symbols": ["cache", "extensions", "children", "id", "isPreloading", "loaded", "parent", "paths", "Module"], "status": "not-implemented" },
|
||||
{ "chapter": "modules", "symbols": ["node:", "node_modules"], "status": "partial", "evidence": ["tests/corpus/1598-cjs-builtin-require/main.js", "tests/corpus/1596-cjs-modules/main.js"] },
|
||||
{ "chapter": "esm", "symbols": ["import", "file:", "node:", "await"], "status": "partial", "evidence": ["tests/corpus/950-modules-basic/main.ts", "tests/corpus/2646-top-level-await.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["url", "dirname", "filename", "main"], "status": "partial", "evidence": ["tests/corpus/2701-import-meta/main.mjs"] },
|
||||
{ "chapter": "esm", "symbols": ["meta", "import.meta.resolve", "data:", "require", "__filename", "require.main", "require.resolve", "NODE_PATH", "require.extensions", "require.cache"], "status": "not-implemented" },
|
||||
{ "chapter": "esm", "symbols": ["import.meta.resolve"], "status": "partial", "evidence": ["tests/corpus/2858-import-meta-resolve/main.mjs"] },
|
||||
{ "chapter": "esm", "symbols": ["meta", "data:", "require", "__filename", "require.main", "require.resolve", "NODE_PATH", "require.extensions", "require.cache"], "status": "not-implemented" },
|
||||
{ "chapter": "packages", "symbols": ["package.json", "\"name\"", "\"main\"", "\"type\"", "\"exports\"", "\"imports\""], "status": "partial", "evidence": ["tests/corpus/2092-package-imports/main.ts", "tests/corpus/2120-package-self-import/main.ts", "tests/corpus/2124-imports-field-wildcard/main.ts", "tests/corpus/2390-dot-requires/main.cjs", "tests/corpus/2830-cjs-require-self-name/main.cjs"] },
|
||||
{ "chapter": "packages", "symbols": ["--input-type"], "status": "not-applicable" },
|
||||
{ "chapter": "child_process", "signatures": ["`subprocess[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/corpus/2844-node-disposable-resources.ts"] },
|
||||
|
||||
+12
-4
@@ -307,7 +307,14 @@ declare var global: typeof globalThis;
|
||||
* to the ENTRY module's record ({ filename } — a compiled binary's main
|
||||
* module is always the entry, Node's answer for a directly-run script);
|
||||
* the rest fences per site. */
|
||||
declare var require: {
|
||||
interface ScriptcRequireResolveOptions {
|
||||
paths?: string[];
|
||||
}
|
||||
interface ScriptcRequireResolve {
|
||||
(id: string, options?: ScriptcRequireResolveOptions): string;
|
||||
paths(id: string): string[] | null;
|
||||
}
|
||||
interface ScriptcRequire {
|
||||
/* The call signature types NON-IMPORT-SHAPED require expressions (the
|
||||
* checker models import-shaped requires as module aliases regardless,
|
||||
* exactly like under @types/node's NodeRequire). `any`, like
|
||||
@@ -315,9 +322,10 @@ declare var require: {
|
||||
* value model — reached uses fence per site. */
|
||||
(id: string): any;
|
||||
main: { filename: string } | undefined;
|
||||
resolve(id: string): string;
|
||||
resolve: ScriptcRequireResolve;
|
||||
cache: { [id: string]: unknown };
|
||||
};
|
||||
}
|
||||
declare var require: ScriptcRequire;
|
||||
|
||||
/* setImmediate/clearImmediate — Node's macrotask pair (fires after I/O
|
||||
* events of the current loop turn, before timers due later). The handle
|
||||
@@ -914,7 +922,7 @@ declare var performance: import("node:perf_hooks").Performance;
|
||||
* name the builtin, like in Node (the builtin wins over the npm package
|
||||
* named "module" for the bare specifier there too). */
|
||||
declare module "node:module" {
|
||||
export function createRequire(filename: string | URL): (id: string) => unknown;
|
||||
export function createRequire(filename: string | URL): ScriptcRequire;
|
||||
export const builtinModules: string[];
|
||||
export function isBuiltin(moduleName: string): boolean;
|
||||
export function syncBuiltinESMExports(): void;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
trackedAccessibleEntries,
|
||||
trackedDirectoryExists,
|
||||
trackedFileExists,
|
||||
markFrontendInputsUnstable,
|
||||
trackedReadFile,
|
||||
validFrontendInputSnapshot,
|
||||
} from "./input-tracker.js";
|
||||
@@ -86,6 +87,14 @@ test("a candidate appearing during the frontend prevents cache publication", asy
|
||||
expect(frontendInputsStillMatch(snapshot)).toBe(false);
|
||||
});
|
||||
|
||||
test("an opaque host query can decline frontend cache publication", () => {
|
||||
const tracker = new FrontendInputTracker();
|
||||
tracker.run(() => markFrontendInputsUnstable());
|
||||
const snapshot = tracker.snapshot();
|
||||
expect(snapshot.stable).toBe(false);
|
||||
expect(validFrontendInputSnapshot(snapshot)).toBe(false);
|
||||
});
|
||||
|
||||
test("directory enumeration invalidates workspace discovery", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-inputs-"));
|
||||
scratch.push(dir);
|
||||
|
||||
@@ -118,6 +118,16 @@ export class FrontendInputTracker {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prevent this frontend result from entering a persistent cache.
|
||||
*
|
||||
* Some host APIs perform semantic filesystem discovery internally and do
|
||||
* not expose the candidates they inspected. Callers may still use their
|
||||
* exact answer for the current build, but must decline cache publication
|
||||
* rather than pretend the observation set is complete. */
|
||||
markUnstable(): void {
|
||||
this.stable = false;
|
||||
}
|
||||
|
||||
snapshot(): FrontendInputSnapshot {
|
||||
return {
|
||||
version: 1,
|
||||
@@ -133,6 +143,12 @@ function record(probe: FrontendInputProbe): void {
|
||||
activeTracker.getStore()?.record(probe);
|
||||
}
|
||||
|
||||
/** Mark the active frontend as unsafe to persist because an exact host query
|
||||
* bypassed the tracked filesystem wrappers. No-op outside a frontend run. */
|
||||
export function markFrontendInputsUnstable(): void {
|
||||
activeTracker.getStore()?.markUnstable();
|
||||
}
|
||||
|
||||
export function trackedReadFile(path: string): string | null {
|
||||
path = resolve(path);
|
||||
try {
|
||||
|
||||
@@ -8,11 +8,12 @@ import { dirname, resolve } from "node:path";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { PoisonError, dynUndefinedExpr, ladderFenceExpr, nodeThrowExpr, own } from "./lowerer.js";
|
||||
import { canonicalBuiltinModule, isJsSourceFile, locOf, requireSpecOf } from "../program.js";
|
||||
import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf, requireSpecOf } from "../program.js";
|
||||
import { isRelativeSpecifier } from "../workspace-registry.js";
|
||||
import { probeNodeRequireRefusal } from "../npm.js";
|
||||
import { isNpmStaticPackage } from "../npm-static.js";
|
||||
import { trackedReadFile } from "../input-tracker.js";
|
||||
import { requireResolvePathsRuntime, resolveImportMetaRuntime, resolveRequireRuntime, type RuntimeResolveError, type RuntimeResolveResult } from "../runtime-resolve.js";
|
||||
import { invalidJsonModuleDiag, requiresDynamicImportDiag } from "../../diagnostics/diagnostic.js";
|
||||
import {
|
||||
BuiltinModuleFn,
|
||||
@@ -623,6 +624,169 @@ function lowerBuiltinOptionalDefault(
|
||||
};
|
||||
}
|
||||
|
||||
function staticString(node: ts.Expression | undefined): string | null {
|
||||
if (node === undefined) return null;
|
||||
const value = stripTypeCasts(node);
|
||||
return ts.isStringLiteralLike(value) ? value.text : null;
|
||||
}
|
||||
|
||||
function runtimeResolveThrow(error: RuntimeResolveError, type: IrType, loc: SrcLoc): IrExpr {
|
||||
return nodeThrowExpr(error.name === "TypeError" ? 1 : 0, error.code, error.message, type, loc);
|
||||
}
|
||||
|
||||
/** import.meta.resolve("literal") folds through the runtime-module
|
||||
* resolver, never TypeScript's declaration-file resolver. Relative and
|
||||
* URL-like names remain valid even when no file exists; bare packages use
|
||||
* Node's import-condition exports path. */
|
||||
export function lowerImportMetaResolveCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
): IrExpr | null {
|
||||
const callee = call.expression;
|
||||
if (
|
||||
!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined ||
|
||||
callee.name.text !== "resolve" || !ts.isMetaProperty(callee.expression) ||
|
||||
callee.expression.keywordToken !== ts.SyntaxKind.ImportKeyword ||
|
||||
callee.expression.name.text !== "meta"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (call.questionDotToken !== undefined || call.arguments.length !== 1) {
|
||||
lowerer.noLowering(
|
||||
"import.meta.resolve with this argument shape",
|
||||
call,
|
||||
"the lowered form is import.meta.resolve(\"<static specifier>\") using the containing module as its parent",
|
||||
);
|
||||
}
|
||||
const specifier = staticString(call.arguments[0]);
|
||||
if (specifier === null) {
|
||||
lowerer.noLowering(
|
||||
"import.meta.resolve with a runtime-computed specifier",
|
||||
call.arguments[0]!,
|
||||
"a compiled binary has a fixed module graph — pass a string literal",
|
||||
);
|
||||
}
|
||||
const result = resolveImportMetaRuntime(
|
||||
call.getSourceFile().fileName,
|
||||
specifier,
|
||||
lowerer.targetPlatform,
|
||||
);
|
||||
if (result === null) {
|
||||
lowerer.noLowering(
|
||||
`import.meta.resolve of '${specifier}'`,
|
||||
call,
|
||||
"relative paths, URL-like names, builtins, and installed package names are supported; package-import aliases remain unsupported",
|
||||
);
|
||||
}
|
||||
const loc = locOf(call);
|
||||
return result.ok
|
||||
? { kind: "strLit", value: result.value, type: STRING, loc }
|
||||
: runtimeResolveThrow(result.error, STRING, loc);
|
||||
}
|
||||
|
||||
function requireResolverBaseFile(lowerer: Lowerer, receiver: ts.Expression): ts.SourceFile | null {
|
||||
const created = createRequireCalleeFileOf(lowerer, receiver);
|
||||
if (created !== null) return created;
|
||||
return lowerer.isStdlibGlobal(receiver, "require") && !isNodeEsmFile(receiver.getSourceFile())
|
||||
? receiver.getSourceFile()
|
||||
: null;
|
||||
}
|
||||
|
||||
function staticResolveOptionPaths(node: ts.Expression | undefined): readonly string[] | undefined | null {
|
||||
if (node === undefined) return undefined;
|
||||
const value = stripTypeCasts(node);
|
||||
if (ts.isIdentifier(value) && value.text === "undefined") return undefined;
|
||||
if (!ts.isObjectLiteralExpression(value)) return null;
|
||||
let paths: readonly string[] | undefined;
|
||||
for (const prop of value.properties) {
|
||||
if (!ts.isPropertyAssignment(prop)) return null;
|
||||
const name = ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name) ? prop.name.text : null;
|
||||
if (name !== "paths" || paths !== undefined) return null;
|
||||
const init = stripTypeCasts(prop.initializer);
|
||||
if (!ts.isArrayLiteralExpression(init)) return null;
|
||||
const entries: string[] = [];
|
||||
for (const item of init.elements) {
|
||||
if (!ts.isStringLiteralLike(item)) return null;
|
||||
entries.push(item.text);
|
||||
}
|
||||
paths = entries;
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
/** CommonJS require.resolve and require.resolve.paths for the ambient
|
||||
* wrapper or a supported createRequire binding. Results are build-time
|
||||
* constants; failures lower to Node's catchable error object. */
|
||||
export function lowerRequireResolveCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
): IrExpr | null {
|
||||
const callee = call.expression;
|
||||
if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined) return null;
|
||||
let receiver: ts.Expression;
|
||||
let pathsCall = false;
|
||||
if (callee.name.text === "resolve") {
|
||||
receiver = callee.expression;
|
||||
} else if (
|
||||
callee.name.text === "paths" && ts.isPropertyAccessExpression(callee.expression) &&
|
||||
callee.expression.questionDotToken === undefined && callee.expression.name.text === "resolve"
|
||||
) {
|
||||
receiver = callee.expression.expression;
|
||||
pathsCall = true;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const baseFile = requireResolverBaseFile(lowerer, receiver);
|
||||
if (baseFile === null) return null;
|
||||
if (call.questionDotToken !== undefined || call.arguments.length < 1 || call.arguments.length > (pathsCall ? 1 : 2)) {
|
||||
lowerer.noLowering(
|
||||
pathsCall ? "require.resolve.paths with this argument shape" : "require.resolve with this argument shape",
|
||||
call,
|
||||
);
|
||||
}
|
||||
const specifier = staticString(call.arguments[0]);
|
||||
if (specifier === null) {
|
||||
lowerer.noLowering(
|
||||
`${pathsCall ? "require.resolve.paths" : "require.resolve"} with a runtime-computed request`,
|
||||
call.arguments[0]!,
|
||||
"a compiled binary has a fixed module graph — pass a string literal",
|
||||
);
|
||||
}
|
||||
const loc = locOf(call);
|
||||
if (pathsCall) {
|
||||
const result = requireResolvePathsRuntime(baseFile.fileName, specifier, lowerer.targetPlatform);
|
||||
if (result !== null && !Array.isArray(result)) {
|
||||
return runtimeResolveThrow(result as RuntimeResolveError, lowerer.irTypeOf(call), loc);
|
||||
}
|
||||
const raw: IrExpr = result === null
|
||||
? { kind: "unitLit", unit: "null", type: NULL_T, loc }
|
||||
: {
|
||||
kind: "arrayLit",
|
||||
elems: result.map((value) => ({ kind: "strLit", value, type: STRING, loc })),
|
||||
type: arrayOf(STRING),
|
||||
loc,
|
||||
};
|
||||
return lowerer.coerceInto(call, raw, lowerer.irTypeOf(call));
|
||||
}
|
||||
const paths = staticResolveOptionPaths(call.arguments[1]);
|
||||
if (paths === null) {
|
||||
lowerer.noLowering(
|
||||
"require.resolve with runtime-computed options",
|
||||
call.arguments[1]!,
|
||||
"omit options or pass { paths: [\"<static directory>\", ...] }",
|
||||
);
|
||||
}
|
||||
const result: RuntimeResolveResult = resolveRequireRuntime(
|
||||
baseFile.fileName,
|
||||
specifier,
|
||||
lowerer.targetPlatform,
|
||||
paths,
|
||||
);
|
||||
return result.ok
|
||||
? { kind: "strLit", value: result.value, type: STRING, loc }
|
||||
: runtimeResolveThrow(result.error, STRING, loc);
|
||||
}
|
||||
|
||||
/** The builtin modules whose `constants` object bakes as literals at
|
||||
* every access site (the fs.constants precedent, scaled up): http2's
|
||||
* full Node v24 table, and crypto's OpenSSL-constant table. The object
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ffiBindingDiag, ffiSignatureDiag, libCallbackDiag, requiresDynamicDiag
|
||||
import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js";
|
||||
import { mixinFnShapeOf } from "./lower-mixins.js";
|
||||
import { bufEncoding, dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerRegexMethodCall, lowerStringMethodCall, lowerTupleReadMethodCall } from "./lower-containers.js";
|
||||
import { lowerChildStreamMethodCall, lowerCreateRequireCall, lowerDirentMethodCall, lowerFileHandleMethodCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerWatcherMethodCall } from "./lower-builtins.js";
|
||||
import { lowerChildStreamMethodCall, lowerCreateRequireCall, lowerDirentMethodCall, lowerFileHandleMethodCall, lowerImportMetaResolveCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerRequireResolveCall, lowerWatcherMethodCall } from "./lower-builtins.js";
|
||||
import { droppableStatic, lowerAbsenceProbe, lowerPromiseAllTupleCall, lowerPromiseRejectCall, probeLower, templateRawTextOf } from "./lower-exprs.js";
|
||||
import { httpClientFnBindingOf, isStreamUndefCallExpr, lowerCompatReqStreamOptionalCall, lowerHttpClientFnCall } from "./lower-server.js";
|
||||
import { EMITTER_API_MEMBERS, exactInstanceClassOf, findGenericMethodOn, lowerClassGenericMethodCall, lowerStaticMethodCall, type ClassInfo } from "./lower-classes.js";
|
||||
@@ -3050,6 +3050,11 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
if (crServed) return crServed;
|
||||
}
|
||||
|
||||
{
|
||||
const resolved = lowerImportMetaResolveCall(lowerer, expr) ?? lowerRequireResolveCall(lowerer, expr);
|
||||
if (resolved) return resolved;
|
||||
}
|
||||
|
||||
// `process.getuid?.()` — intercepted BEFORE the optional-chain
|
||||
// machinery (the member always exists on a POSIX target, so the
|
||||
// optional call IS the call; `process.getuid` itself has no value
|
||||
|
||||
@@ -2005,13 +2005,6 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
|
||||
const sf = expr.getSourceFile();
|
||||
const fileName = moduleFileName(lowerer, sf);
|
||||
if (lowerer.dynamic) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
`'import.meta.${expr.name.text}' in a dynamic island (module-loader metadata is supported only in static ESM)`,
|
||||
);
|
||||
}
|
||||
switch (expr.name.text) {
|
||||
case "url":
|
||||
return {
|
||||
@@ -4088,7 +4081,7 @@ export function lowerOptionalChain(lowerer: Lowerer, expr: ts.CallExpression | t
|
||||
}
|
||||
lowerer.badType(expr, tsType);
|
||||
}
|
||||
let type = mapped as IrType & { kind: "array" };
|
||||
const type = mapped as IrType & { kind: "array" };
|
||||
// Keep the array payload type fixed. Optional reads are stored through
|
||||
// arrayValueStore, which records UNDEFINED in the state byte while the
|
||||
// payload remains number/string/etc.; widening the array element here
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { moduleSpecifiersOf } from "./npm.js";
|
||||
import { NpmGraphBuilder } from "./npm.js";
|
||||
|
||||
test("collects import.meta.resolve literals as resolution-only edges", () => {
|
||||
const result = moduleSpecifiersOf(
|
||||
'export const relative = import.meta.resolve("./asset.js");\n' +
|
||||
'export const packageUrl = import.meta.resolve("example-package");\n',
|
||||
"/project/index.mjs",
|
||||
);
|
||||
expect(result.uses).toEqual([
|
||||
{
|
||||
specifier: "./asset.js",
|
||||
static: false,
|
||||
require: false,
|
||||
requireLocal: false,
|
||||
requireViaHelper: false,
|
||||
dynamicImport: false,
|
||||
importMetaResolve: true,
|
||||
},
|
||||
{
|
||||
specifier: "example-package",
|
||||
static: false,
|
||||
require: false,
|
||||
requireLocal: false,
|
||||
requireViaHelper: false,
|
||||
dynamicImport: false,
|
||||
importMetaResolve: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("emits an import-condition edge for an embedded bare import.meta.resolve", () => {
|
||||
const root = new URL("../../../../tests/fixtures/npm/", import.meta.url).pathname;
|
||||
const entry = `${root}cases/dual-entry/main.ts`;
|
||||
const builder = new NpmGraphBuilder();
|
||||
builder.addImport(entry, "dual");
|
||||
const graph = builder.finish();
|
||||
const dual = graph.modules.find((module) => module.key.endsWith("/dual/index.mjs"));
|
||||
expect(dual).toBeDefined();
|
||||
expect(graph.edges).toContainEqual({
|
||||
from: dual!.key,
|
||||
specifier: "cjszoo",
|
||||
to: expect.stringMatching(/\/cjszoo\/index\.js$/),
|
||||
kind: "import",
|
||||
});
|
||||
});
|
||||
|
||||
test("runtime introspection keeps import and require export conditions separate", () => {
|
||||
const root = new URL("../../../../tests/fixtures/npm/", import.meta.url).pathname;
|
||||
const entry = `${root}cases/dual-entry/main.ts`;
|
||||
const builder = new NpmGraphBuilder();
|
||||
expect(builder.resolveForIntrospection(entry, "dual", "import")).toMatch(/\/dual\/index\.mjs$/);
|
||||
expect(builder.resolveForIntrospection(entry, "dual", "require")).toMatch(/\/dual\/index\.cjs$/);
|
||||
});
|
||||
@@ -596,7 +596,7 @@ const KNOWN_BUILTINS = new Set([
|
||||
* several forms in one file; any STATIC occurrence makes the edge eager
|
||||
* (Node refuses the whole static graph at link time regardless of what the
|
||||
* lazy sites would have done). */
|
||||
interface SpecifierUse {
|
||||
export interface SpecifierUse {
|
||||
specifier: string;
|
||||
/** import/export declaration — eager, link-time. */
|
||||
static: boolean;
|
||||
@@ -616,12 +616,16 @@ interface SpecifierUse {
|
||||
requireViaHelper: boolean;
|
||||
/** import("x") — evaluation-time. */
|
||||
dynamicImport: boolean;
|
||||
/** import.meta.resolve("x") — resolves synchronously without loading.
|
||||
* It still needs an emitted edge for bare package names so the island can
|
||||
* answer from its fixed graph; relative and URL-like names need no edge. */
|
||||
importMetaResolve: boolean;
|
||||
}
|
||||
|
||||
/** moduleSpecifiersOf's full answer: the per-specifier call-site kinds
|
||||
* plus where the file's `__require` binding comes from, when it is not
|
||||
* its own. */
|
||||
interface ModuleSpecifiers {
|
||||
export interface ModuleSpecifiers {
|
||||
uses: SpecifierUse[];
|
||||
/** The specifier `__require` is IMPORTED from (`import { __require }
|
||||
* from "./chunk-X.js"` — esbuild's shared-helper chunk shape), else
|
||||
@@ -641,7 +645,7 @@ interface ModuleSpecifiers {
|
||||
* __require("literal") (esbuild's external-require helper — collecting its
|
||||
* literal call sites gives bundled dists an honest build-time inventory).
|
||||
* A real parse, never a regex. */
|
||||
function moduleSpecifiersOf(source: string, fileName: string): ModuleSpecifiers {
|
||||
export function moduleSpecifiersOf(source: string, fileName: string): ModuleSpecifiers {
|
||||
const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, false, ts.ScriptKind.JS);
|
||||
const uses: SpecifierUse[] = [];
|
||||
const bySpec = new Map<string, SpecifierUse>();
|
||||
@@ -660,6 +664,7 @@ function moduleSpecifiersOf(source: string, fileName: string): ModuleSpecifiers
|
||||
requireLocal: false,
|
||||
requireViaHelper: false,
|
||||
dynamicImport: false,
|
||||
importMetaResolve: false,
|
||||
};
|
||||
bySpec.set(spec, use);
|
||||
uses.push(use);
|
||||
@@ -698,6 +703,14 @@ function moduleSpecifiersOf(source: string, fileName: string): ModuleSpecifiers
|
||||
ts.isStringLiteralLike(arg)
|
||||
) {
|
||||
push(arg.text, "dynamicImport");
|
||||
} else if (
|
||||
ts.isPropertyAccessExpression(n.expression) &&
|
||||
n.expression.name.text === "resolve" &&
|
||||
ts.isMetaProperty(n.expression.expression) &&
|
||||
n.expression.expression.keywordToken === ts.SyntaxKind.ImportKeyword &&
|
||||
n.arguments.length >= 1 && arg !== undefined && ts.isStringLiteralLike(arg)
|
||||
) {
|
||||
push(arg.text, null).importMetaResolve = true;
|
||||
} else if (
|
||||
ts.isIdentifier(n.expression) &&
|
||||
(n.expression.text === "require" || n.expression.text === "__require") &&
|
||||
@@ -945,6 +958,27 @@ export class NpmGraphBuilder {
|
||||
|
||||
constructor(private readonly host: Host = realHost) {}
|
||||
|
||||
/** Resolve one runtime module without embedding it. This is the package
|
||||
* half of import.meta.resolve/require.resolve: the answer is executable
|
||||
* source under Node's import/require conditions, never the declaration-file
|
||||
* answer from resolve.ts. Diagnostics produced by the graph-oriented
|
||||
* resolver are discarded here; the introspection lowering supplies Node's
|
||||
* public error shape separately. */
|
||||
resolveForIntrospection(
|
||||
fromFile: string,
|
||||
specifier: string,
|
||||
mode: "import" | "require",
|
||||
): string | null {
|
||||
const before = this.errors.length;
|
||||
const key = this.resolvePackage(dirname(resolve(fromFile)), specifier, mode, {
|
||||
importer: resolve(fromFile),
|
||||
chain: [],
|
||||
preferModuleField: false,
|
||||
});
|
||||
this.errors.length = before;
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Registers one user-level npm import and walks everything it reaches.
|
||||
* Idempotent per (importing dir, specifier). */
|
||||
addImport(fromFile: string, specifier: string): void {
|
||||
@@ -1349,7 +1383,7 @@ export class NpmGraphBuilder {
|
||||
fromDir: string,
|
||||
specifier: string,
|
||||
mode: "import" | "require",
|
||||
ctx: { importer: string; chain: readonly string[] },
|
||||
ctx: { importer: string; chain: readonly string[]; preferModuleField?: boolean },
|
||||
): string | null {
|
||||
const name = packageNameOf(specifier);
|
||||
const parts = specifier.split("/");
|
||||
@@ -1414,7 +1448,10 @@ export class NpmGraphBuilder {
|
||||
target = fromExports;
|
||||
} else if (subpath !== ".") {
|
||||
target = subpath;
|
||||
} else if (mode === "import" && typeof pkg.module === "string" && pkg.module !== "") {
|
||||
} else if (
|
||||
mode === "import" && ctx.preferModuleField !== false &&
|
||||
typeof pkg.module === "string" && pkg.module !== ""
|
||||
) {
|
||||
// No "exports": the "module" field names the ESM build (a
|
||||
// bundler convention Node itself ignores; embedding prefers the
|
||||
// real ES module over require-shimming the CJS "main").
|
||||
@@ -1575,6 +1612,9 @@ export class NpmGraphBuilder {
|
||||
const spec = use.specifier;
|
||||
const eager = !lazy && use.static;
|
||||
if (spec.startsWith("./") || spec.startsWith("../")) {
|
||||
if (use.importMetaResolve && !use.static && !use.dynamicImport && !use.require) {
|
||||
continue;
|
||||
}
|
||||
// A relative file: no conditions apply, so every call form shares
|
||||
// one "any" edge. A blocked lazy one embeds the import trap only
|
||||
// for import()/static-in-lazy sites — require-reached specs embed
|
||||
@@ -1605,6 +1645,12 @@ export class NpmGraphBuilder {
|
||||
this.walk(to, chain, lazy || !use.static);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
use.importMetaResolve && !use.static && !use.dynamicImport && !use.require &&
|
||||
(/^[A-Za-z][A-Za-z\d+.-]*:/.test(spec) || spec.startsWith("/"))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const builtin = builtinKeyOf(spec);
|
||||
if (builtin) {
|
||||
let seen = this.builtinsSeen.get(builtin);
|
||||
@@ -1691,7 +1737,7 @@ export class NpmGraphBuilder {
|
||||
this.walk(to, nextChain, true);
|
||||
}
|
||||
}
|
||||
if (use.static || use.dynamicImport) {
|
||||
if (use.static || use.dynamicImport || use.importMetaResolve) {
|
||||
const errorsBefore = this.errors.length;
|
||||
const to = this.resolvePackage(dirname(key), spec, "import", { importer: key, chain });
|
||||
if (to === null) {
|
||||
@@ -1724,7 +1770,9 @@ export class NpmGraphBuilder {
|
||||
);
|
||||
}
|
||||
pushEdge(key, spec, to, "import");
|
||||
this.walk(to, nextChain, lazy || !use.static);
|
||||
if (!use.importMetaResolve || use.static || use.dynamicImport) {
|
||||
this.walk(to, nextChain, lazy || !use.static);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { join } from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
import { resolveImportMetaRuntime, runtimePathForTarget } from "./runtime-resolve.js";
|
||||
|
||||
test("WASI resolution metadata uses guest paths", () => {
|
||||
const source = join(process.cwd(), "tests", "program", "main.mjs");
|
||||
expect(runtimePathForTarget(source, "wasi")).toBe("/tests/program/main.mjs");
|
||||
expect(resolveImportMetaRuntime(source, "./asset with space.js", "wasi")).toEqual({
|
||||
ok: true,
|
||||
value: "file:///tests/program/asset%20with%20space.js",
|
||||
});
|
||||
});
|
||||
|
||||
test("Windows resolution metadata uses target URL semantics on any host", () => {
|
||||
expect(resolveImportMetaRuntime("C:\\work\\main.mjs", "./asset.js", "win32")).toEqual({
|
||||
ok: true,
|
||||
value: "file:///C:/work/asset.js",
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
/* Node runtime resolution used by compile-time-folded module introspection.
|
||||
*
|
||||
* This is deliberately separate from resolve.ts: that resolver answers the
|
||||
* TypeScript program's TYPE surface and may correctly select a .d.ts file,
|
||||
* while import.meta.resolve / require.resolve must answer Node's executable
|
||||
* file or URL. CJS resolution delegates to the pinned Node 24 host for exact
|
||||
* exports/main/extension/error behavior. The host API does not expose its
|
||||
* failed lookup candidates, so those builds decline persistent frontend-cache
|
||||
* publication rather than retaining an incomplete dependency snapshot. ESM
|
||||
* package success uses the tracked embedded-graph resolver under Node's import
|
||||
* conditions; relative and URL-like specifiers are pure URL resolution and do
|
||||
* not require the target to exist, matching Node. */
|
||||
|
||||
import { createRequire, isBuiltin } from "node:module";
|
||||
import { isAbsolute } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { markFrontendInputsUnstable } from "./input-tracker.js";
|
||||
import { NpmGraphBuilder, probeNodeImportRefusal } from "./npm.js";
|
||||
import { wasiGuestPath } from "../wasi-paths.js";
|
||||
|
||||
export interface RuntimeResolveError {
|
||||
name: string;
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type RuntimeResolveResult =
|
||||
| { ok: true; value: string }
|
||||
| { ok: false; error: RuntimeResolveError };
|
||||
|
||||
function errorShape(error: unknown): RuntimeResolveError {
|
||||
const e = error as { name?: unknown; code?: unknown; message?: unknown };
|
||||
return {
|
||||
name: typeof e?.name === "string" ? e.name : "Error",
|
||||
code: typeof e?.code === "string" ? e.code : "",
|
||||
message: typeof e?.message === "string" ? e.message : String(error),
|
||||
};
|
||||
}
|
||||
|
||||
export function runtimePathForTarget(path: string, targetPlatform: string): string {
|
||||
if (targetPlatform !== "wasi") return path;
|
||||
return wasiGuestPath(path) ?? path.replaceAll("\\", "/");
|
||||
}
|
||||
|
||||
function targetFileUrl(path: string, targetPlatform: string): string {
|
||||
return pathToFileURL(runtimePathForTarget(path, targetPlatform), {
|
||||
windows: targetPlatform === "win32",
|
||||
}).href;
|
||||
}
|
||||
|
||||
function urlLike(specifier: string): boolean {
|
||||
return /^[A-Za-z][A-Za-z\d+.-]*:/.test(specifier);
|
||||
}
|
||||
|
||||
/** import.meta.resolve for a static string and the containing source file.
|
||||
* Package-import (`#name`) resolution remains a named lowering fence until
|
||||
* its Node error/success path can share the package-import resolver without
|
||||
* crossing back into TypeScript's declaration-file answer. */
|
||||
export function resolveImportMetaRuntime(
|
||||
fromFile: string,
|
||||
specifier: string,
|
||||
targetPlatform: string,
|
||||
): RuntimeResolveResult | null {
|
||||
const base = targetFileUrl(fromFile, targetPlatform);
|
||||
if (
|
||||
specifier.startsWith("./") || specifier.startsWith("../") ||
|
||||
isAbsolute(specifier) || urlLike(specifier)
|
||||
) {
|
||||
try {
|
||||
return { ok: true, value: new URL(specifier, base).href };
|
||||
} catch (error) {
|
||||
return { ok: false, error: errorShape(error) };
|
||||
}
|
||||
}
|
||||
if (isBuiltin(specifier)) {
|
||||
return { ok: true, value: specifier.startsWith("node:") ? specifier : `node:${specifier}` };
|
||||
}
|
||||
if (specifier.startsWith("#")) return null;
|
||||
|
||||
const builder = new NpmGraphBuilder();
|
||||
const key = builder.resolveForIntrospection(fromFile, specifier, "import");
|
||||
if (key !== null) return { ok: true, value: targetFileUrl(key, targetPlatform) };
|
||||
const refusal = probeNodeImportRefusal(fromFile, specifier);
|
||||
if (refusal !== null) {
|
||||
return {
|
||||
ok: false,
|
||||
error: { name: "Error", code: refusal.code, message: refusal.message },
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** require.resolve under the containing file's CommonJS resolver. */
|
||||
export function resolveRequireRuntime(
|
||||
fromFile: string,
|
||||
specifier: string,
|
||||
targetPlatform: string,
|
||||
paths?: readonly string[],
|
||||
): RuntimeResolveResult {
|
||||
markFrontendInputsUnstable();
|
||||
try {
|
||||
const require = createRequire(pathToFileURL(fromFile));
|
||||
const value = paths === undefined
|
||||
? require.resolve(specifier)
|
||||
: require.resolve(specifier, { paths: [...paths] });
|
||||
return {
|
||||
ok: true,
|
||||
value: isAbsolute(value) ? runtimePathForTarget(value, targetPlatform) : value,
|
||||
};
|
||||
} catch (error) {
|
||||
return { ok: false, error: errorShape(error) };
|
||||
}
|
||||
}
|
||||
|
||||
/** require.resolve.paths under the containing file's CommonJS resolver. */
|
||||
export function requireResolvePathsRuntime(
|
||||
fromFile: string,
|
||||
specifier: string,
|
||||
targetPlatform: string,
|
||||
): readonly string[] | null | RuntimeResolveError {
|
||||
markFrontendInputsUnstable();
|
||||
try {
|
||||
const paths = createRequire(pathToFileURL(fromFile)).resolve.paths(specifier);
|
||||
return paths?.map((path) => runtimePathForTarget(path, targetPlatform)) ?? null;
|
||||
} catch (error) {
|
||||
return errorShape(error);
|
||||
}
|
||||
}
|
||||
@@ -6064,6 +6064,24 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/2858-import-meta-resolve/main.mjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/2858-import-meta-resolve/main.mjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/2859-import-meta-resolve-dynamic/main.mjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/2859-import-meta-resolve-dynamic/main.mjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/2860-require-resolve/main.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/2860-require-resolve/main.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/300-if-else.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/300-if-else.ts"
|
||||
@@ -7648,18 +7666,6 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/import-meta-dynamic.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/import-meta-dynamic.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/import-meta-resolve.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/import-meta-resolve.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/import-meta-write.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/import-meta-write.ts"
|
||||
@@ -8007,6 +8013,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"<repo>/tests/diagnostics/module-resolution-introspection.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/module-resolution-introspection.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/diagnostics/multiple-errors.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/multiple-errors.ts"
|
||||
|
||||
@@ -2378,6 +2378,27 @@ static char *isl_module_normalize(JSContext *ctx, const char *base,
|
||||
return js_strdup(ctx, target);
|
||||
}
|
||||
|
||||
/* import.meta.resolve is module-relative, so each compiled module receives a
|
||||
* tiny closure carrying its emitted key. The bootstrap owns the JavaScript
|
||||
* resolution policy (builtins, URL-like names, relative URLs, and known
|
||||
* embedded edges); this C shim only supplies the lexical module identity. */
|
||||
static JSValue isl_import_meta_resolve(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv, int magic,
|
||||
JSValueConst *func_data) {
|
||||
(void)this_val;
|
||||
(void)magic;
|
||||
JSValue global = JS_GetGlobalObject(ctx);
|
||||
JSValue fn = JS_GetPropertyStr(ctx, global, "__scr_import_meta_resolve");
|
||||
JS_FreeValue(ctx, global);
|
||||
if (JS_IsException(fn)) return fn;
|
||||
JSValueConst args[2] = {
|
||||
func_data[0], argc > 0 ? argv[0] : JS_UNDEFINED,
|
||||
};
|
||||
JSValue out = JS_Call(ctx, fn, JS_UNDEFINED, 2, args);
|
||||
JS_FreeValue(ctx, fn);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Named export lists for the builtin ESM wrappers. The wrapper source is
|
||||
* `const m = __scr_require("node:x"); export default m; export const
|
||||
* {…} = m;` — the shims themselves live in the bootstrap below. The
|
||||
@@ -2469,6 +2490,12 @@ static JSModuleDef *isl_module_load(JSContext *ctx, const char *name, void *opaq
|
||||
url = JS_NewString(ctx, name);
|
||||
}
|
||||
JS_SetPropertyStr(ctx, meta, "url", url); /* consumed */
|
||||
JSValue key = JS_NewString(ctx, name);
|
||||
JSValueConst data[1] = {key};
|
||||
JSValue resolve_fn = JS_NewCFunctionData(ctx, isl_import_meta_resolve, 1,
|
||||
0, 1, data);
|
||||
JS_FreeValue(ctx, key); /* resolve_fn's func_data retains it */
|
||||
JS_SetPropertyStr(ctx, meta, "resolve", resolve_fn); /* consumed */
|
||||
JS_FreeValue(ctx, meta);
|
||||
}
|
||||
JS_FreeValue(ctx, v);
|
||||
@@ -2513,8 +2540,10 @@ static JSValue isl_host_resolve(JSContext *ctx, JSValueConst this_val, int argc,
|
||||
if (from) JS_FreeCString(ctx, from);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
/* host.resolve serves the require shim exclusively — require kind. */
|
||||
const char *to = strncmp(spec, "node:", 5) == 0 ? spec : isl_edge_find(from, spec, 2);
|
||||
/* The require shim asks for kind 2; import.meta.resolve passes a true third
|
||||
* argument and asks for kind 1, preserving dual-package conditions. */
|
||||
int want = argc > 2 && JS_ToBool(ctx, argv[2]) > 0 ? 1 : 2;
|
||||
const char *to = strncmp(spec, "node:", 5) == 0 ? spec : isl_edge_find(from, spec, want);
|
||||
JSValue r = to ? JS_NewString(ctx, to) : JS_UNDEFINED;
|
||||
JS_FreeCString(ctx, from);
|
||||
JS_FreeCString(ctx, spec);
|
||||
@@ -3442,6 +3471,7 @@ static const char isl_modules_bootstrap[] =
|
||||
" }\n"
|
||||
" const cache = Object.create(null);\n"
|
||||
" const builtins = Object.create(null);\n"
|
||||
" const builtinModules = ['assert','assert/strict','async_hooks','buffer','child_process','cluster','console','constants','crypto','dgram','diagnostics_channel','dns','dns/promises','domain','events','fs','fs/promises','http','http2','https','inspector','inspector/promises','module','net','os','path','path/posix','path/win32','perf_hooks','process','punycode','querystring','readline','readline/promises','repl','stream','stream/consumers','stream/promises','stream/web','string_decoder','sys','timers','timers/promises','tls','trace_events','tty','url','util','util/types','v8','vm','wasi','worker_threads','zlib'];\n"
|
||||
/* Node's require stack: each CJS module remembers its FIRST requirer
|
||||
* (Node's module.parent / moduleParentCache — the chain is static,
|
||||
* captured at first load, not the dynamic call stack), and a failing
|
||||
@@ -3478,6 +3508,39 @@ static const char isl_modules_bootstrap[] =
|
||||
" }\n"
|
||||
" return to;\n"
|
||||
" };\n"
|
||||
" const requirePathsFrom = (from, spec) => {\n"
|
||||
" if (typeof spec !== 'string') {\n"
|
||||
" const err = new TypeError('The \"request\" argument must be of type string.');\n"
|
||||
" err.code = 'ERR_INVALID_ARG_TYPE';\n"
|
||||
" throw err;\n"
|
||||
" }\n"
|
||||
" const bare = spec.startsWith('node:') ? spec.slice(5) : spec;\n"
|
||||
" if (builtinModules.includes(bare) && (spec.startsWith('node:') || bare !== 'test')) return null;\n"
|
||||
" const slash = from.lastIndexOf('/');\n"
|
||||
" const base = slash < 0 ? '/' : (from.slice(0, slash) || '/');\n"
|
||||
" if (spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/')) return [base];\n"
|
||||
" const out = [];\n"
|
||||
" for (let dir = base;;) {\n"
|
||||
" out.push((dir === '/' ? '' : dir) + '/node_modules');\n"
|
||||
" const at = dir.lastIndexOf('/');\n"
|
||||
" const parent = at <= 0 ? '/' : dir.slice(0, at);\n"
|
||||
" if (parent === dir) break;\n"
|
||||
" dir = parent;\n"
|
||||
" }\n"
|
||||
" return out;\n"
|
||||
" };\n"
|
||||
" const decorateRequire = (req, from) => {\n"
|
||||
" const resolve = (spec) => {\n"
|
||||
" if (typeof spec !== 'string') return requirePathsFrom(from, spec);\n"
|
||||
" const bare = spec.startsWith('node:') ? spec.slice(5) : spec;\n"
|
||||
" if (builtinModules.includes(bare) && (spec.startsWith('node:') || bare !== 'test')) return spec;\n"
|
||||
" return resolveFrom(from, spec);\n"
|
||||
" };\n"
|
||||
" resolve.paths = (spec) => requirePathsFrom(from, spec);\n"
|
||||
" req.resolve = resolve;\n"
|
||||
" req.cache = cache;\n"
|
||||
" return req;\n"
|
||||
" };\n"
|
||||
" const requireKey = (key, parent) => {\n"
|
||||
" if (key.startsWith('node:')) {\n"
|
||||
" const b = builtins[key.slice(5)];\n"
|
||||
@@ -3495,8 +3558,7 @@ static const char isl_modules_bootstrap[] =
|
||||
" if (format === 2) { mod.exports = JSON.parse(src); return mod.exports; }\n"
|
||||
" if (format === 0) { delete cache[key]; throw new Error('require() of ES module ' + key); }\n"
|
||||
" const fn = new Function('exports', 'require', 'module', '__filename', '__dirname', src);\n"
|
||||
" const req = (spec) => requireKey(resolveFrom(key, spec), key);\n"
|
||||
" req.cache = cache;\n"
|
||||
" const req = decorateRequire((spec) => requireKey(resolveFrom(key, spec), key), key);\n"
|
||||
" const dir = key.slice(0, key.lastIndexOf('/')) || '/';\n"
|
||||
/* A module whose evaluation THROWS leaves no cache entry — Node
|
||||
* deletes it so a later require re-evaluates (and a lazy require
|
||||
@@ -4244,15 +4306,12 @@ static const char isl_modules_bootstrap[] =
|
||||
" if (key.startsWith('file://')) key = decodeURIComponent(key.slice(7));\n"
|
||||
/* The base file is the created require's parent, like Node: modules
|
||||
* it loads report it in their require stacks. */
|
||||
" const req = (spec) => requireKey(spec.startsWith('node:') ? spec : resolveFrom(key, spec), key);\n"
|
||||
" req.cache = cache;\n"
|
||||
" return req;\n"
|
||||
" return decorateRequire((spec) => requireKey(spec.startsWith('node:') ? spec : resolveFrom(key, spec), key), key);\n"
|
||||
" };\n"
|
||||
/* builtinModules/isBuiltin answer Node's QUESTION ("is this name a
|
||||
* Node builtin?") with Node's full list — resolution of unshimmed
|
||||
* ones still fails lazily at the call, the island's documented
|
||||
* shape. */
|
||||
" const builtinModules = ['assert','assert/strict','async_hooks','buffer','child_process','cluster','console','constants','crypto','dgram','diagnostics_channel','dns','dns/promises','domain','events','fs','fs/promises','http','http2','https','inspector','inspector/promises','module','net','os','path','path/posix','path/win32','perf_hooks','process','punycode','querystring','readline','readline/promises','repl','stream','stream/consumers','stream/promises','stream/web','string_decoder','sys','timers','timers/promises','tls','trace_events','tty','url','util','util/types','v8','vm','wasi','worker_threads','zlib'];\n"
|
||||
" const isBuiltin = (name) => {\n"
|
||||
" const n = String(name);\n"
|
||||
" return n.startsWith('node:') ? builtinModules.includes(n.slice(5)) : builtinModules.includes(n);\n"
|
||||
@@ -9205,6 +9264,22 @@ static const char isl_modules_bootstrap[] =
|
||||
" }\n"
|
||||
" if (globalThis.global === undefined) globalThis.global = globalThis;\n"
|
||||
" globalThis.__scr_require = requireKey;\n"
|
||||
" globalThis.__scr_import_meta_resolve = (from, raw) => {\n"
|
||||
" const spec = String(raw);\n"
|
||||
" const bare = spec.startsWith('node:') ? spec.slice(5) : spec;\n"
|
||||
" if (builtinModules.includes(bare) && (spec.startsWith('node:') || bare !== 'test')) return 'node:' + bare;\n"
|
||||
" const base = from.startsWith('/') ? new URL('file://' + from).href : from;\n"
|
||||
" if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(spec) || spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/')) {\n"
|
||||
" return new URL(spec, base).href;\n"
|
||||
" }\n"
|
||||
" const to = host.resolve(from, spec, true);\n"
|
||||
" if (to === undefined) {\n"
|
||||
" const err = new Error(\"Cannot find package '\" + spec + \"' imported from \" + from);\n"
|
||||
" err.code = 'ERR_MODULE_NOT_FOUND';\n"
|
||||
" throw err;\n"
|
||||
" }\n"
|
||||
" return to.startsWith('/') ? new URL('file://' + to).href : to;\n"
|
||||
" };\n"
|
||||
" return (key, name) => {\n"
|
||||
" const exports = requireKey(key);\n"
|
||||
" if (name === 'default') return exports;\n"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
// import.meta.resolve is scoped to each compiled module. Relative names are
|
||||
// URLs even when the target does not exist; builtins canonicalize to node:;
|
||||
// URL-like inputs normalize without touching the filesystem; installed bare
|
||||
// packages resolve under Node's ESM import conditions.
|
||||
console.log("relative", import.meta.resolve("./module with space.mjs"));
|
||||
console.log("missing", import.meta.resolve("./missing.mjs"));
|
||||
console.log("builtins", import.meta.resolve("fs"), import.meta.resolve("node:path"));
|
||||
console.log("url", import.meta.resolve("https://example.com/a/../b"));
|
||||
console.log("data", import.meta.resolve("data:text/javascript,export default 1"));
|
||||
console.log("package", import.meta.resolve("typescript").endsWith("/typescript/lib/typescript.js"));
|
||||
const require = createRequire(import.meta.url);
|
||||
console.log("createRequire", require.resolve("./module with space.mjs"));
|
||||
console.log("createRequire core paths", require.resolve.paths("fs"));
|
||||
try {
|
||||
import.meta.resolve("definitely-not-installed-here");
|
||||
console.log("unexpected resolution");
|
||||
} catch (error) {
|
||||
console.log("missing package", error.code, error.message.split("\n")[0]);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const marker = "resolved";
|
||||
@@ -0,0 +1,9 @@
|
||||
// @dynamic
|
||||
// Embedding the package engine does not move the program's own ESM modules
|
||||
// into it: their metadata remains per-file compile-time identity.
|
||||
console.log(import.meta.url);
|
||||
console.log(import.meta.filename);
|
||||
console.log(import.meta.dirname);
|
||||
console.log(import.meta.main);
|
||||
console.log(import.meta.resolve("./missing package module.mjs"));
|
||||
console.log(import.meta.resolve("fs"));
|
||||
@@ -0,0 +1 @@
|
||||
{ "resolved": true }
|
||||
@@ -0,0 +1,19 @@
|
||||
// CommonJS resolution introspection returns filenames without evaluating the
|
||||
// target. The options.paths form searches from its supplied directories, and
|
||||
// missing requests throw Node's catchable MODULE_NOT_FOUND shape.
|
||||
console.log("relative", require.resolve("./cfg.json"));
|
||||
console.log("builtins", require.resolve("fs"), require.resolve("node:path"));
|
||||
console.log("core paths", require.resolve.paths("fs"));
|
||||
console.log("relative paths", JSON.stringify(require.resolve.paths("./cfg.json")));
|
||||
console.log(
|
||||
"custom paths",
|
||||
require.resolve("fs", {
|
||||
paths: ["tests/corpus/2860-require-resolve/vendor"],
|
||||
}),
|
||||
);
|
||||
try {
|
||||
require.resolve("./missing.cjs");
|
||||
console.log("unexpected resolution");
|
||||
} catch (error) {
|
||||
console.log("missing", error.code, error.message.split("\n")[0]);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { createRequire } from "node:module";
|
||||
// The base names some OTHER file: unrecognized — the member fence.
|
||||
const req = createRequire("/tmp/parent.js");
|
||||
console.log(String(req("./config.cjs")));
|
||||
|
||||
// The supported base still fences dynamic require calls and program-module loads.
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
// A computed specifier: fenced by name.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// @dynamic
|
||||
const url = import.meta.url;
|
||||
console.log(url);
|
||||
@@ -1,2 +0,0 @@
|
||||
const resolved = import.meta.resolve("./module.mjs");
|
||||
console.log(resolved);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const specifier = process.env.MODULE_NAME ?? "./fallback.mjs";
|
||||
console.log(import.meta.resolve(specifier));
|
||||
// createRequire uses the same static-literal boundary.
|
||||
const require = createRequire(import.meta.url);
|
||||
console.log(require.resolve(specifier));
|
||||
console.log(require.resolve("./fallback.mjs", { paths: process.argv }));
|
||||
// Dynamic requests and dynamic search paths remain explicit refusals.
|
||||
@@ -12,6 +12,11 @@ const require = createRequire(import.meta.url);
|
||||
// A plain CJS package: module.exports members through the island.
|
||||
const zoo = require("cjszoo") as any;
|
||||
console.log("zoo:", zoo.alpha as string, zoo.extra as string, zoo.gamma() as string);
|
||||
console.log(
|
||||
"zoo resolve:",
|
||||
(zoo.resolved as string).endsWith("/cjszoo/core.js"),
|
||||
(zoo.resolvePaths as string[])[0]?.endsWith("/cjszoo"),
|
||||
);
|
||||
|
||||
// The dual package: "exports" splits import/require — require() loads
|
||||
// the CJS arm, exactly Node's createRequire.
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
// @dynamic
|
||||
// Dual-published package: the ESM ("import" condition) entry wins, exactly
|
||||
// like Node resolving an import statement.
|
||||
import { flavor } from "dual";
|
||||
import { cjsUrl, flavor, zooUrl } from "dual";
|
||||
|
||||
const f: string = flavor;
|
||||
console.log(f);
|
||||
console.log(cjsUrl.endsWith("/dual/index.cjs"));
|
||||
console.log(zooUrl.endsWith("/cjszoo/index.js"));
|
||||
|
||||
+2
@@ -3,3 +3,5 @@
|
||||
"use strict";
|
||||
module.exports = require("./core.js");
|
||||
module.exports.extra = "extra";
|
||||
module.exports.resolved = require.resolve("./core.js");
|
||||
module.exports.resolvePaths = require.resolve.paths("./core.js");
|
||||
|
||||
+2
@@ -1 +1,3 @@
|
||||
export declare const flavor: string;
|
||||
export declare const cjsUrl: string;
|
||||
export declare const zooUrl: string;
|
||||
|
||||
+2
@@ -1 +1,3 @@
|
||||
export const flavor = "esm";
|
||||
export const cjsUrl = import.meta.resolve("./index.cjs");
|
||||
export const zooUrl = import.meta.resolve("cjszoo");
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
create-require.ts:13:7 - error SC2011: values of type 'ScriptcRequire' have no static representation but run in the embedded dynamic engine, which this build does not include
|
||||
|
||||
12 | // The base names some OTHER file: unrecognized — the member fence.
|
||||
13 | const req = createRequire("/tmp/parent.js");
|
||||
| ^~~
|
||||
14 | console.log(String(req("./config.cjs")));
|
||||
|
||||
hint: build with --dynamic to run these values in the embedded engine (adds ~620KB to the binary), or restate the type inside the static surface (https://scriptc.dev/limitations describes the boundary)
|
||||
|
||||
create-require.ts:13:13 - error SC2020: 'module.createRequire' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
12 | // The base names some OTHER file: unrecognized — the member fence.
|
||||
@@ -7,6 +16,15 @@ create-require.ts:13:13 - error SC2020: 'module.createRequire' is part of the st
|
||||
|
||||
hint: the lowered shape is a const binding over createRequire(import.meta.url) (or __filename) whose require calls take STATIC string literals — builtins, relative .json documents, and installed npm packages (under --dynamic) resolve at build time; dynamic specifiers cannot exist in a compiled binary's fixed module graph
|
||||
|
||||
create-require.ts:14:20 - error SC2004: uses of 'req' inherit the blocker on its declaration
|
||||
|
||||
13 | const req = createRequire("/tmp/parent.js");
|
||||
14 | console.log(String(req("./config.cjs")));
|
||||
| ^~~
|
||||
15 | // The supported base still fences dynamic require calls and program-module loads.
|
||||
|
||||
hint: the declaration of 'req' did not compile — fix the diagnostic reported there and these sites clear with it
|
||||
|
||||
create-require.ts:20:1 - error SC2020: 'createRequire's require with this argument shape' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
19 | const which = "left" + "-pad";
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import-meta-dynamic.ts:2:13 - error SC1090: 'import.meta.url' in a dynamic island (module-loader metadata is supported only in static ESM) is not supported yet
|
||||
|
||||
1 | // @dynamic
|
||||
2 | const url = import.meta.url;
|
||||
| ^~~~~~~~~~~~~~~
|
||||
3 | console.log(url);
|
||||
@@ -1,7 +0,0 @@
|
||||
import-meta-resolve.ts:1:18 - error SC2020: 'ImportMeta.resolve' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
1 | const resolved = import.meta.resolve("./module.mjs");
|
||||
| ^~~~~~~~~~~~~~~~~~~
|
||||
2 | console.log(resolved);
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
@@ -0,0 +1,26 @@
|
||||
module-resolution-introspection.ts:4:33 - error SC2020: 'import.meta.resolve with a runtime-computed specifier' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
3 | const specifier = process.env.MODULE_NAME ?? "./fallback.mjs";
|
||||
4 | console.log(import.meta.resolve(specifier));
|
||||
| ^~~~~~~~~
|
||||
5 | // createRequire uses the same static-literal boundary.
|
||||
|
||||
hint: a compiled binary has a fixed module graph — pass a string literal
|
||||
|
||||
module-resolution-introspection.ts:7:29 - error SC2020: 'require.resolve with a runtime-computed request' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
6 | const require = createRequire(import.meta.url);
|
||||
7 | console.log(require.resolve(specifier));
|
||||
| ^~~~~~~~~
|
||||
8 | console.log(require.resolve("./fallback.mjs", { paths: process.argv }));
|
||||
|
||||
hint: a compiled binary has a fixed module graph — pass a string literal
|
||||
|
||||
module-resolution-introspection.ts:8:47 - error SC2020: 'require.resolve with runtime-computed options' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
7 | console.log(require.resolve(specifier));
|
||||
8 | console.log(require.resolve("./fallback.mjs", { paths: process.argv }));
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~
|
||||
9 | // Dynamic requests and dynamic search paths remain explicit refusals.
|
||||
|
||||
hint: omit options or pass { paths: ["<static directory>", ...] }
|
||||
Reference in New Issue
Block a user