UNPKG

globload

Version:

A Node.js module loader for importing files using glob patterns, supporting specific exports and YAML parsing.

105 lines (101 loc) 4.76 kB
import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import yaml from "js-yaml"; import { glob } from "tinyglobby"; //#region src/utils.ts function normalizePath(p) { return path.posix.normalize(p).replace(/\\/g, "/"); } function createURL(input, base) { if (compareNodeVersions(process.version, "v22.1") >= 0) return URL.parse(input, base); return new URL(input, base); } function compareNodeVersions(v1, v2) { const parseVersion = (vStr) => { const normalized = vStr.startsWith("v") ? vStr.substring(1) : vStr; const parts = normalized.split(".").map(Number); return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 }; }; const ver1 = parseVersion(v1); const ver2 = parseVersion(v2); if (ver1.major !== ver2.major) return ver1.major > ver2.major ? 1 : -1; if (ver1.minor !== ver2.minor) return ver1.minor > ver2.minor ? 1 : -1; if (ver1.patch !== ver2.patch) return ver1.patch > ver2.patch ? 1 : -1; return 0; } //#endregion //#region src/loader.ts const resolve = (specifier, context, nextResolve) => { const url = createURL(specifier, context.parentURL); if (!url?.searchParams.has("glob")) return nextResolve(specifier, context); return { shortCircuit: true, url: url.toString(), importAttributes: context.importAttributes }; }; const load = async (url, context, nextLoad) => { const absoluteUrl = createURL(url); if (!absoluteUrl?.searchParams.has("glob")) return nextLoad(url, context); const absoluteGlobPattern = normalizePath(fileURLToPath(absoluteUrl.toString())); const searchParams = absoluteUrl.searchParams; const mode = searchParams.has("eager") ? "eager" : "lazy"; const importKey = searchParams.get("import"); const files = await glob(absoluteGlobPattern, { absolute: true }); let importCounter = 0; const importStatements = []; const properties = []; const importAttributesJson = JSON.stringify(context.importAttributes); const hasImportAttributes = Object.keys(context.importAttributes).length > 0; for (const absoluteFilePath of files) { const fileExtension = path.extname(absoluteFilePath).toLowerCase(); const isYaml = [".yaml", ".yml"].includes(fileExtension); const moduleFileUrl = pathToFileURL(absoluteFilePath).toString(); const relativePathKey = normalizePath(path.relative(process.cwd(), absoluteFilePath)); if (isYaml) if (mode === "eager") { const fileContent = await fs.readFile(absoluteFilePath, "utf-8"); let parsedContent = yaml.load(fileContent); if (importKey && importKey !== "default" && typeof parsedContent === "object" && parsedContent !== null) parsedContent = parsedContent[importKey]; properties.push(`'${relativePathKey}': ${JSON.stringify(parsedContent)}`); } else { const physicalPath = normalizePath(fileURLToPath(moduleFileUrl)); let propertyValue; if (importKey && importKey !== "default") propertyValue = `async () => { const c = await import('node:fs/promises').then(({ readFile }) => readFile('${physicalPath}', 'utf-8')); const p = await import('js-yaml').then(({ load }) => load(c)); return typeof p === 'object' && p !== null ? p['${importKey}'] : undefined; }`; else propertyValue = `async () => { const c = await import('node:fs/promises').then(({ readFile }) => readFile('${physicalPath}', 'utf-8')); return await import('js-yaml').then(({ load }) => load(c)); }`; properties.push(`'${relativePathKey}': ${propertyValue}`); } else if (mode === "eager") { const importName = `__globbed_eager_${importCounter++}`; let importStatement; if (importKey) if (importKey === "default") importStatement = `import { default as ${importName} } from '${moduleFileUrl}' ${hasImportAttributes ? `with ${importAttributesJson}` : ""};`; else importStatement = `import { ${importKey} as ${importName} } from '${moduleFileUrl}' ${hasImportAttributes ? `with ${importAttributesJson}` : ""};`; else importStatement = `import * as ${importName} from '${moduleFileUrl}' ${hasImportAttributes ? `with ${importAttributesJson}` : ""};`; importStatements.push(importStatement); properties.push(`'${relativePathKey}': ${importName}`); } else { let importAccess = ""; if (importKey) if (importKey === "default") importAccess = ".then(m => m.default)"; else importAccess = `.then(m => m.${importKey})`; properties.push(`'${relativePathKey}': () => import('${moduleFileUrl}' ${hasImportAttributes ? `, { with: ${importAttributesJson} }` : ""})${importAccess}`); } } const source = ` // Dynamically generated by globload ${importStatements.join("\n")} export default { ${properties.join(",\n")} }; `; return { shortCircuit: true, format: "module", source }; }; //#endregion export { load, resolve };