UNPKG

importx

Version:

Unified tool for importing TypeScript modules at runtime

201 lines (197 loc) 6.61 kB
import { fileURLToPath, pathToFileURL } from 'node:url'; import Debug from 'debug'; import { dirname, join } from 'pathe'; let _loaderMatrix; async function _createLoaderMatrix(options) { const matrix = [ { name: "native", cache: [true], listDependencies: [false], type: ["module", "commonjs"], importTS: options.isNativeTsImportSupported ? [true, false] : [false] }, { name: "tsx", supported: options.isRuntimeSupportsTsx, type: ["module", "commonjs"], cache: [true, false], listDependencies: [true, false], importTS: [true, false] }, { name: "jiti", type: ["commonjs"], cache: [true, false], listDependencies: [true, false], importTS: [true, false] }, { name: "bundle-require", type: ["module", "commonjs"], cache: [false], listDependencies: [true], importTS: [true, false] } ]; return matrix.filter((i) => i.supported !== false); } async function getLoaderMatrix() { if (_loaderMatrix) return _loaderMatrix; _loaderMatrix = await _createLoaderMatrix({ isNativeTsImportSupported: await isNativeTsImportSupported(), isRuntimeSupportsTsx: await isRuntimeSupportsTsx() }); return _loaderMatrix; } let _isNativeTsImportSupported; async function isNativeTsImportSupported() { if (_isNativeTsImportSupported === undefined) { if (typeof process !== "undefined" && process.features?.typescript) { return _isNativeTsImportSupported = true; } try { const modName = "dummy.mts"; const mod = await import(`../runtime-fixtures/${modName}`); _isNativeTsImportSupported = mod.default === "dummy"; } catch { _isNativeTsImportSupported = false; } } return _isNativeTsImportSupported; } const nodeVersionNumbers = globalThis?.process?.versions?.node?.split(".").map(Number); async function detectLoader(context, matrix) { matrix = matrix || await getLoaderMatrix(); for (const loader of matrix) { if (context.excludeLoaders?.includes(loader.name)) continue; if ((context.cache === null || loader.cache.includes(context.cache)) && (context.listDependencies === null || loader.listDependencies.includes(context.listDependencies)) && (context.type === null || loader.type.includes(context.type)) && loader.importTS.includes(context.isTs)) { return loader.name; } } return null; } async function isRuntimeSupportsTsx() { if (!nodeVersionNumbers || nodeVersionNumbers[0] < 18 || nodeVersionNumbers[0] === 18 && nodeVersionNumbers[1] < 19 || nodeVersionNumbers[0] === 20 && nodeVersionNumbers[1] < 8) { return false; } const msBuildVersion = typeof process !== "undefined" && process.versions["microsoft-build"]; if (msBuildVersion && Number(msBuildVersion) < 10629634) { return false; } return true; } const reIsTypeScriptFile = /\.[mc]?tsx?$/; function isTypeScriptFile(path) { return reIsTypeScriptFile.test(path); } const debug = Debug("importx"); const _moduleInfoMap = /* @__PURE__ */ new WeakMap(); function getModuleInfo(mod) { return _moduleInfoMap.get(mod); } async function importx(_specifier, _options) { const options = typeof _options === "string" || _options instanceof URL ? { parentURL: _options } : _options; const { loaderOptions = {}, parentURL: inputUserUrl, cache = null, type = null, excludeLoaders = [], listDependencies = null, ignoreImportxWarning = false, fallbackLoaders = ["jiti"], ...otherOptions } = options; let specifier = _specifier instanceof URL ? fileURLToPath(_specifier) : _specifier; specifier = specifier.match(/^[a-z]:/i) ? pathToFileURL(specifier).href : specifier; let loader = options.loader || getLoaderFromEnv() || "auto"; if (loader === "auto") { const context = { cache, listDependencies, type, isTs: isTypeScriptFile(specifier), excludeLoaders }; const _loader = await detectLoader(context); if (!_loader) throw new Error(`[importx] Cannot find a suitable loader for given requirements ${JSON.stringify(context)}`); loader = _loader; } const parentPath = typeof inputUserUrl === "string" && !inputUserUrl.includes("://") ? inputUserUrl : fileURLToPath(inputUserUrl); const parentURL = pathToFileURL(parentPath); const fullPath = specifier[0] === "." ? fileURLToPath(new URL(specifier, parentURL)) : specifier; const info = { loader, cache, specifier, fullPath, parentURL, parentPath, timestampInit: Date.now(), timestampLoad: -1 }; async function run(loader2) { info.loader = loader2; debug(`[${loader2}]`, "Importing", fullPath, "from", parentPath); switch (loader2) { case "native": { if (cache === false && !ignoreImportxWarning) throw new Error("`cache: false` is not compatible with `native` loader"); return import(fullPath, otherOptions); } case "tsx": { return import('./chunks/tsx.mjs').then((r) => r.loader(info, options)); } case "jiti": { return import('./chunks/jiti.mjs').then((r) => r.loader(info, options)); } case "bundle-require": { if (cache === true && !ignoreImportxWarning) throw new Error("`cache: true` is not compatible with `native` loader"); const cwd = dirname(parentPath); return import('bundle-require').then((r) => r.bundleRequire({ format: type === "commonjs" ? "cjs" : "esm", ...loaderOptions.bundleRequire, filepath: fullPath, cwd })).then((r) => { info.dependencies = r.dependencies.map((d) => pathToFileURL(join(cwd, d)).href); return r.mod; }); } default: { throw new Error(`Unknown loader: ${loader2}`); } } } const loaders = /* @__PURE__ */ new Set([ loader, ...fallbackLoaders || [] ]); let error; for (const loader2 of loaders) { try { const mod = await run(loader2); info.timestampLoad = Date.now(); const previous = _moduleInfoMap.get(mod); if (previous) info.previousImportInfo = previous; _moduleInfoMap.set(mod, info); return mod; } catch (err) { error = err; debug(`[${loader2}]`, err); } } throw error; } function getLoaderFromEnv() { if (typeof process !== "undefined" && process.env) return process.env.IMPORTX_LOADER || undefined; return undefined; } export { getModuleInfo, importx as import, importx, isNativeTsImportSupported, isTypeScriptFile };