89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
|
|
/**
|
|||
|
|
* 文件索引管理器
|
|||
|
|
* 支持任意文件类型的索引加载和缓存
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
type FileIndex = Record<string, string>;
|
|||
|
|
|
|||
|
|
let fileIndex: FileIndex | null = null;
|
|||
|
|
let indexLoadPromise: Promise<void> | null = null;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载文件索引(只加载一次)
|
|||
|
|
* 支持 CLI 环境(从 JSON 加载)和 Dev 环境(使用 webpackContext 仅加载 .md 文件)
|
|||
|
|
*/
|
|||
|
|
function ensureIndexLoaded(): Promise<void> {
|
|||
|
|
if (indexLoadPromise) return indexLoadPromise;
|
|||
|
|
|
|||
|
|
indexLoadPromise = (async () => {
|
|||
|
|
// 尝试 CLI 环境:从 /__FILE_INDEX.json 加载
|
|||
|
|
try {
|
|||
|
|
const response = await fetch("/__FILE_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<string> {
|
|||
|
|
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<string[]> {
|
|||
|
|
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;
|
|||
|
|
}
|