mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Node islands stack, part 2 of 11 (scriptc-perf/results/islands-prs/PLAN.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
4.2 KiB
JavaScript
143 lines
4.2 KiB
JavaScript
// @scriptc/node — the Node.js implementation.
|
|
//
|
|
// scriptc builds lower call() and callSync() to calls into Node.js loaded
|
|
// into the program (packages/runtime/src/scr_node_island.c and its
|
|
// bootstrap, scr_node_island_host.cjs). Under Node.js they run directly,
|
|
// with the same copies at the boundary, so a program prints the same
|
|
// either way. Keep the two in step: every message and copy rule here has
|
|
// its twin in the bootstrap.
|
|
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
// Error classes that cross as copies, at the host's kind index.
|
|
const ERROR_CLASSES = [
|
|
Error,
|
|
TypeError,
|
|
RangeError,
|
|
SyntaxError,
|
|
undefined,
|
|
ReferenceError,
|
|
EvalError,
|
|
URIError,
|
|
];
|
|
const ERROR_KINDS = [
|
|
[TypeError, 1],
|
|
[RangeError, 2],
|
|
[SyntaxError, 3],
|
|
[ReferenceError, 5],
|
|
[EvalError, 6],
|
|
[URIError, 7],
|
|
];
|
|
|
|
/** The file of the code that called `api`. */
|
|
function callerFile(api) {
|
|
const savedPrepare = Error.prepareStackTrace;
|
|
const savedLimit = Error.stackTraceLimit;
|
|
let sites;
|
|
try {
|
|
Error.stackTraceLimit = 1;
|
|
Error.prepareStackTrace = (_error, callSites) => callSites;
|
|
const holder = {};
|
|
Error.captureStackTrace(holder, api);
|
|
sites = holder.stack;
|
|
} finally {
|
|
Error.prepareStackTrace = savedPrepare;
|
|
Error.stackTraceLimit = savedLimit;
|
|
}
|
|
const name = Array.isArray(sites) && sites.length > 0 ? sites[0].getFileName() : undefined;
|
|
if (typeof name !== "string")
|
|
throw new Error("@scriptc/node: cannot determine the calling module");
|
|
return name.startsWith("file:") ? fileURLToPath(name) : name;
|
|
}
|
|
|
|
const requires = new Map();
|
|
|
|
function exportedFunction(file, specifier, name) {
|
|
let load = requires.get(file);
|
|
if (load === undefined) {
|
|
load = createRequire(file);
|
|
requires.set(file, load);
|
|
}
|
|
const namespace = load(specifier);
|
|
const fn = namespace === null || namespace === undefined ? undefined : namespace[name];
|
|
if (typeof fn !== "function")
|
|
throw new TypeError(`'${name}' exported by '${specifier}' is not a function`);
|
|
return { fn, namespace };
|
|
}
|
|
|
|
function isThenable(value) {
|
|
return (
|
|
value !== null &&
|
|
(typeof value === "object" || typeof value === "function") &&
|
|
typeof value.then === "function"
|
|
);
|
|
}
|
|
|
|
function errorKind(error) {
|
|
for (const [constructor, kind] of ERROR_KINDS) if (error instanceof constructor) return kind;
|
|
return 0;
|
|
}
|
|
|
|
/** The copy of a thrown value that the caller sees. */
|
|
function copyThrown(error) {
|
|
if (error instanceof Error) {
|
|
const copy = new ERROR_CLASSES[errorKind(error)](String(error.message));
|
|
if (typeof error.code === "string") copy.code = error.code;
|
|
return copy;
|
|
}
|
|
let text;
|
|
try {
|
|
text = JSON.stringify(error);
|
|
} catch {
|
|
text = undefined;
|
|
}
|
|
return text === undefined ? undefined : JSON.parse(text);
|
|
}
|
|
|
|
/** The JSON copy of a result; throws what JSON.stringify throws. */
|
|
function copyResult(value) {
|
|
const text = JSON.stringify(value);
|
|
return text === undefined ? undefined : JSON.parse(text);
|
|
}
|
|
|
|
function copyArgs(args) {
|
|
return JSON.parse(JSON.stringify(args));
|
|
}
|
|
|
|
export function callSync(specifier, name, ...args) {
|
|
const file = callerFile(callSync);
|
|
const copied = copyArgs(args);
|
|
let result;
|
|
try {
|
|
const { fn, namespace } = exportedFunction(file, specifier, name);
|
|
result = Reflect.apply(fn, namespace, copied);
|
|
if (isThenable(result)) {
|
|
// The promise is dropped: its rejection must not end the process.
|
|
Promise.resolve(result).catch(() => {});
|
|
throw new TypeError(
|
|
`callSync: '${name}' exported by '${specifier}' returned a promise; use call() for asynchronous functions`,
|
|
);
|
|
}
|
|
return copyResult(result);
|
|
} catch (error) {
|
|
throw copyThrown(error);
|
|
}
|
|
}
|
|
|
|
export async function call(specifier, name, ...args) {
|
|
const file = callerFile(call);
|
|
const copied = copyArgs(args);
|
|
try {
|
|
const { fn, namespace } = exportedFunction(file, specifier, name);
|
|
return copyResult(await Reflect.apply(fn, namespace, copied));
|
|
} catch (error) {
|
|
throw copyThrown(error);
|
|
}
|
|
}
|
|
|
|
/** requireModule(specifier): the module's exports, by reference. */
|
|
export function requireModule(specifier) {
|
|
return createRequire(callerFile(requireModule))(specifier);
|
|
}
|