mock-import
Version:
Mocking of Node.js EcmaScript modules
185 lines (149 loc) • 4.7 kB
JavaScript
import {createRequire, isBuiltin} from 'node:module';
import process from 'node:process';
import tryCatch from 'try-catch';
import {convertImports} from './convert-imports/index.js';
import {convertTracedImports} from './convert-traced-imports/index.js';
import {traceImports} from './trace-imports/index.js';
global.__mockImportCounter = global.__mockImportCounter || 0;
globalThis.__mockImportCache = global.__mockImportCache || new Map();
globalThis.__mockImportReImports = global.__mockImportReImports || new Set();
global.__traceImportCache = global.__traceImportCache || new Map();
global.__nextCount = () => ++global.__mockImportCounter;
global.__reImport = async (moduleUrl, url) => {
const {resolve} = createRequire(url);
const reFn = reImport({
resolve,
});
return await reFn(moduleUrl);
};
global.__mockImportNested = Boolean(process.env.MOCK_IMPORT_NESTED);
const cache = global.__mockImportCache;
const traceCache = global.__traceImportCache;
const reImports = global.__mockImportReImports;
const nextCount = global.__nextCount;
export const enableNestedImports = () => {
global.__mockImportNested = true;
globalThis.__mockImportLoader.enableNestedImports();
};
export const disableNestedImports = () => {
global.__mockImportNested = false;
globalThis.__mockImportLoader.disableNestedImports();
};
export const createMockImport = (url) => {
const {resolve} = createRequire(url);
return {
mockImport: mockImport({
resolve,
}),
reImport: reImport({
resolve,
}),
reImportDefault: reImportDefault({
resolve,
}),
traceImport: traceImport({
resolve,
}),
reTrace: reTrace({
resolve,
}),
stop: stop({
resolve,
}),
stopAll,
};
};
export {initialize} from './loader.js';
function transform({url, source, pathname, resolve}) {
let code = source;
if (reImports.has(pathname)) {
const [sourceFileName] = String(url).split('?');
code = convertImports({
sourceFileName,
resolve,
source: code,
cache,
nested: global.__mockImportNested,
});
}
if (traceCache.has(pathname)) {
const stack = traceCache.get(pathname);
code = traceImports({
url,
stack,
source: code,
});
}
if (traceCache.size)
code = convertTracedImports({
source: code,
resolve,
traceCache,
nextCount,
});
return code;
}
export async function load(url, context, defaultLoad) {
const {pathname} = new URL(url);
const {format, source: rawSource} = await defaultLoad(url, context, defaultLoad);
const source = String(rawSource);
if (format === 'commonjs')
return {
format,
};
if (url.startsWith('node:'))
return {
source,
format,
};
const {resolve} = createRequire(pathname);
const code = transform({
url,
source,
pathname,
resolve,
});
return {
source: code,
format,
};
}
const traceImport = ({resolve}) => (name, {stack}) => {
const [, pathname = name] = tryCatch(resolve, name);
traceCache.set(pathname, stack);
globalThis.__mockImportLoader.setTraceCache(pathname);
};
const reTrace = ({resolve}) => async (name) => {
const pathname = resolve(name);
return await import(`${pathname}?mock-import-count=${nextCount()}`);
};
const mockImport = ({resolve}) => (name, data) => {
const [, pathname = name] = tryCatch(resolve, name);
cache.set(pathname, data);
globalThis.__mockImportLoader.setCache(pathname);
};
const reImport = ({resolve}) => async (name) => {
if (isBuiltin(name))
return await import(name);
const pathname = resolve(name);
reImports.add(pathname);
globalThis.__mockImportLoader.addReImport(pathname);
return await import(`${pathname}?mock-import-count=${nextCount()}`);
};
const reImportDefault = ({resolve}) => async (name) => {
const imported = await reImport({
resolve,
})(name);
return imported.default;
};
const stop = ({resolve}) => (name) => {
const pathname = resolve(name);
cache.delete(pathname);
globalThis.__mockImportLoader.stopCache(pathname);
};
const stopAll = () => {
cache.clear();
reImports.clear();
traceCache.clear();
globalThis.__mockImportLoader.stopAll();
};