nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
244 lines (201 loc) • 6.07 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/test_runner/mock/loader.js
import {
ensureNodeScheme,
kBadExportsMessage,
kMockSearchParam,
kMockSuccess,
kMockExists,
kMockUnknownMessage,
} from "nstdlib/lib/internal/test_runner/mock/mock";
import { pathToFileURL, URL } from "nstdlib/lib/internal/url";
import { normalizeReferrerURL } from "nstdlib/lib/internal/modules/helpers";
import { createRequire, isBuiltin } from "nstdlib/lib/module";
import { defaultGetFormatWithoutErrors } from "nstdlib/lib/internal/modules/esm/get_format";
import { defaultResolve } from "nstdlib/lib/internal/modules/esm/resolve";
// TODO(cjihrig): The mocks need to be thread aware because the exports are
// evaluated on the thread that creates the mock. Before marking this API as
// stable, one of the following issues needs to be implemented:
// https://github.com/nodejs/node/issues/49472
// or https://github.com/nodejs/node/issues/52219
const mocks = new Map();
async function initialize(data) {
data?.port.on("message", ({ type, payload }) => {
{
/* debug */
}
if (type === "node:test:register") {
const { baseURL } = payload;
const mock = mocks.get(baseURL);
if (mock?.active) {
{
/* debug */
}
sendAck(payload.ack, kMockExists);
return;
}
const localVersion = mock?.localVersion ?? 0;
{
/* debug */
}
mocks.set(baseURL, {
__proto__: null,
active: true,
cache: payload.cache,
exportNames: payload.exportNames,
format: payload.format,
hasDefaultExport: payload.hasDefaultExport,
localVersion,
url: baseURL,
});
sendAck(payload.ack);
} else if (type === "node:test:unregister") {
const mock = mocks.get(payload.baseURL);
if (mock !== undefined) {
mock.active = false;
mock.localVersion++;
}
sendAck(payload.ack);
} else {
sendAck(payload.ack, kMockUnknownMessage);
}
});
}
async function resolve(specifier, context, nextResolve) {
{
/* debug */
}
let mockSpecifier;
if (isBuiltin(specifier)) {
mockSpecifier = ensureNodeScheme(specifier);
} else {
let format;
if (context.parentURL) {
format = defaultGetFormatWithoutErrors(pathToFileURL(context.parentURL));
}
try {
if (format === "module") {
specifier = defaultResolve(specifier, context).url;
} else {
specifier = pathToFileURL(
createRequire(context.parentURL).resolve(specifier),
).href;
}
} catch {
const parentURL = normalizeReferrerURL(context.parentURL);
const parsedURL = URL.parse(specifier, parentURL)?.href;
if (parsedURL) {
specifier = parsedURL;
}
}
mockSpecifier = specifier;
}
const mock = mocks.get(mockSpecifier);
{
/* debug */
}
if (mock?.active !== true) {
return nextResolve(specifier, context);
}
const url = new URL(mockSpecifier);
url.searchParams.set(kMockSearchParam, mock.localVersion);
if (!mock.cache) {
// With ESM, we can't remove modules from the cache. Bump the module's
// version instead so that the next import will be uncached.
mock.localVersion++;
}
{
/* debug */
}
return nextResolve(url.href, context);
}
async function load(url, context, nextLoad) {
{
/* debug */
}
const parsedURL = URL.parse(url);
if (parsedURL) {
parsedURL.searchParams.delete(kMockSearchParam);
}
const baseURL = parsedURL ? parsedURL.href : url;
const mock = mocks.get(baseURL);
const original = await nextLoad(url, context);
{
/* debug */
}
if (mock?.active !== true) {
return original;
}
// Treat builtins as commonjs because customization hooks do not allow a
// core module to be replaced.
// Also collapse 'commonjs-sync' and 'require-commonjs' to 'commonjs'.
const format =
original.format === "builtin" ||
original.format === "commonjs-sync" ||
original.format === "require-commonjs"
? "commonjs"
: original.format;
const result = {
__proto__: null,
format,
shortCircuit: true,
source: await createSourceFromMock(mock, format),
};
{
/* debug */
}
return result;
}
async function createSourceFromMock(mock, format) {
// Create mock implementation from provided exports.
const { exportNames, hasDefaultExport, url } = mock;
const useESM = format === "module";
const source = `${testImportSource(useESM)}
if (!$__test.mock._mockExports.has('${url}')) {
throw new Error(${JSONStringify(`mock exports not found for "${url}"`)});
}
const $__exports = $__test.mock._mockExports.get(${JSONStringify(url)});
${defaultExportSource(useESM, hasDefaultExport)}
${namedExportsSource(useESM, exportNames)}
`;
return source;
}
function testImportSource(useESM) {
if (useESM) {
return "import $__test from 'node:test';";
}
return "const $__test = require('node:test');";
}
function defaultExportSource(useESM, hasDefaultExport) {
if (!hasDefaultExport) {
return "";
} else if (useESM) {
return "export default $__exports.defaultExport;";
}
return "module.exports = $__exports.defaultExport;";
}
function namedExportsSource(useESM, exportNames) {
let source = "";
if (!useESM && exportNames.length > 0) {
source += `
if (module.exports === null || typeof module.exports !== 'object') {
throw new Error('${JSONStringify(kBadExportsMessage)}');
}
`;
}
for (let i = 0; i < exportNames.length; ++i) {
const name = exportNames[i];
if (useESM) {
source += `export let ${name} = $__exports.namedExports[${JSONStringify(name)}];\n`;
} else {
source += `module.exports[${JSONStringify(name)}] = $__exports.namedExports[${JSONStringify(name)}];\n`;
}
}
return source;
}
function sendAck(buf, status = kMockSuccess) {
AtomicsStore(buf, 0, status);
AtomicsNotify(buf, 0);
}
export { initialize };
export { load };
export { resolve };