/** * 文件索引管理器 * 支持任意文件类型的索引加载和缓存 */ type FileIndex = Record; let fileIndex: FileIndex | null = null; let indexLoadPromise: Promise | null = null; /** * 加载文件索引(只加载一次) * 支持 CLI 环境(从 JSON 加载)和 Dev 环境(使用 webpackContext 仅加载 .md 文件) */ function ensureIndexLoaded(): Promise { if (indexLoadPromise) return indexLoadPromise; indexLoadPromise = (async () => { // 尝试 CLI 环境:从 /__CONTENT_INDEX.json 加载 try { const response = await fetch("/__CONTENT_INDEX.json"); if (response.ok) { const index = await response.json(); fileIndex = { ...fileIndex, ...index }; return; } } catch (e) { // CLI 索引不可用时尝试 dev 环境 } // Dev 环境:使用 import.meta.webpackContext + raw loader 加载 .md, .csv, .yarn 文件 try { const context = import.meta.webpackContext("../../content", { recursive: true, regExp: /\.md|\.yarn|\.csv$/i, }); const keys = context.keys(); const index: FileIndex = {}; for (const key of keys) { // context 返回的是模块,需要访问其 default 导出(raw-loader 处理后的内容) const module = context(key) as { default?: string } | string; const content = typeof module === "string" ? module : module.default ?? ""; const normalizedPath = "/content" + key.slice(1); index[normalizedPath] = content; } fileIndex = { ...fileIndex, ...index }; } catch (e) { // webpackContext 不可用时忽略 } })(); return indexLoadPromise; } /** * 从索引获取文件内容 */ export async function getIndexedData(path: string): Promise { await ensureIndexLoaded(); if (fileIndex && fileIndex[path]) { return fileIndex[path]; } const res = await fetch(path); const content = await res.text(); fileIndex = fileIndex || {}; fileIndex[path] = content; return content; } /** * 获取指定扩展名的文件路径 */ export async function getPathsByExtension(ext: string): Promise { await ensureIndexLoaded(); if (!fileIndex) return []; const normalizedExt = ext.startsWith(".") ? ext : `.${ext}`; return Object.keys(fileIndex).filter(path => path.toLowerCase().endsWith(normalizedExt.toLowerCase()) ); } /** * 清除索引(用于测试或重新加载) */ export function clearIndex(): void { fileIndex = null; indexLoadPromise = null; }