mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Keep a probe load eager when it catches the refusal of a program file
A package that catches the probe's refusal to load a program file evaluated a fallback, and the probe recorded that load as clean and snapshotted the fallback's exports, which the executable never has. A load in which the resolve hook refused a program file is now recorded as throwing the refusal whether or not the load caught it, and so is a later load that reaches one of its files through the module cache.
This commit is contained in:
@@ -72,3 +72,25 @@ test("the probe never evaluates the program's own source", () => {
|
||||
expect(existsSync(join(root, "src/evaluated-program"))).toBe(false);
|
||||
expect(existsSync(join(root, "src/evaluated-shared"))).toBe(false);
|
||||
});
|
||||
|
||||
test("a package that catches the refusal does not defer, nor does one that loads it later", () => {
|
||||
const root = project({
|
||||
"src/config.js": marker("config"),
|
||||
"node_modules/catches/package.json": '{ "name": "catches" }',
|
||||
"node_modules/catches/index.js":
|
||||
'let config;\ntry { config = require("../../src/config.js"); } catch { config = { fallback: true }; }\nmodule.exports = config;\n',
|
||||
"node_modules/later/package.json": '{ "name": "later" }',
|
||||
"node_modules/later/index.js": 'module.exports = { config: require("catches") };\n',
|
||||
"node_modules/clean/package.json": '{ "name": "clean" }',
|
||||
"node_modules/clean/index.js": "module.exports = { ok: true };\n",
|
||||
});
|
||||
const results = probe(root, [
|
||||
join(root, "node_modules/catches/index.js"),
|
||||
join(root, "node_modules/later/index.js"),
|
||||
join(root, "node_modules/clean/index.js"),
|
||||
]);
|
||||
const refusal = `the build does not evaluate ${join(root, "src/config.js")}, a file of the program`;
|
||||
expect(results.map((r) => r.throws)).toEqual([refusal, refusal, undefined]);
|
||||
expect(results.map((r) => r.root === undefined)).toEqual([true, true, false]);
|
||||
expect(existsSync(join(root, "src/evaluated-config"))).toBe(false);
|
||||
});
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
* observable by other island modules evaluated out of order);
|
||||
* - every file the load required (the module's closure), and a throw;
|
||||
* a load that resolves a file outside every node_modules directory (the
|
||||
* program's own source) throws before that file evaluates;
|
||||
* program's own source) throws before that file evaluates, and its
|
||||
* record is that throw even when the load catches it;
|
||||
*
|
||||
* and takes the interface snapshot of the module's value (snapshot.ts).
|
||||
* Calls made by Node's own internals (the module loader reading files,
|
||||
@@ -190,6 +191,11 @@ exports.probe = function probe(input) {
|
||||
const inputs = [], volatile = [], effects = [], tty = new Map(), envKeys = new Map();
|
||||
const files = new Set();
|
||||
let recording = false;
|
||||
// The first program file the current load asked for (the resolve hook),
|
||||
// and, for every file a load with a refusal evaluated, that program file:
|
||||
// the module cache keeps what such a file exported without it.
|
||||
let refused = null;
|
||||
const refusedBy = new Map();
|
||||
const note = (list, what) => {
|
||||
if (recording && !internalCaller()) list.push(list === volatile ? what : what + " (" + lastCaller + ")");
|
||||
};
|
||||
@@ -440,8 +446,10 @@ exports.probe = function probe(input) {
|
||||
const result = nextResolve(specifier, context);
|
||||
if (recording && typeof result.url === "string" && result.url.startsWith("file:")) {
|
||||
const file = require("node:url").fileURLToPath(result.url);
|
||||
if (!/[\\/]node_modules[\\/]/.test(file))
|
||||
if (!/[\\/]node_modules[\\/]/.test(file)) {
|
||||
if (refused === null) refused = file;
|
||||
throw new Error("the build does not evaluate " + file + ", a file of the program");
|
||||
}
|
||||
files.add(file);
|
||||
}
|
||||
return result;
|
||||
@@ -511,6 +519,7 @@ exports.probe = function probe(input) {
|
||||
pendingTimers.clear();
|
||||
loadPromises.clear();
|
||||
tty.clear(); envKeys.clear(); files.clear();
|
||||
refused = null;
|
||||
files.add(path);
|
||||
currentFiles = files;
|
||||
currentPackageDir = null;
|
||||
@@ -522,6 +531,15 @@ exports.probe = function probe(input) {
|
||||
recording = true;
|
||||
try { exportsValue = createRequire(path)(path); } catch (e) { threw = String((e && e.message) || e); }
|
||||
recording = false;
|
||||
// A load that catches the refusal evaluates a fallback the executable
|
||||
// never runs: its exports are not the module's, and neither are those of
|
||||
// a later load that reaches one of its files through the module cache.
|
||||
if (refused === null)
|
||||
for (const f of files) if (refusedBy.has(f)) { refused = refusedBy.get(f); break; }
|
||||
if (refused !== null) {
|
||||
threw = "the build does not evaluate " + refused + ", a file of the program";
|
||||
for (const f of files) if (!refusedBy.has(f)) refusedBy.set(f, refused);
|
||||
}
|
||||
const after = shot();
|
||||
const root = threw === undefined ? value(exportsValue, 0) : undefined;
|
||||
const mutations = [];
|
||||
|
||||
Reference in New Issue
Block a user