refactor: only index md files

This commit is contained in:
hypercross 2026-02-26 14:14:26 +08:00
parent 34c2f9e346
commit c6889ac65d
2 changed files with 58 additions and 54 deletions

View File

@ -1,9 +1,9 @@
import type { ServeCommandHandler } from '../types.js'; import type { ServeCommandHandler } from "../types.js";
import { createServer, Server, IncomingMessage, ServerResponse } from 'http'; import { createServer, Server, IncomingMessage, ServerResponse } from "http";
import { readdirSync, statSync, readFileSync, existsSync } from 'fs'; import { readdirSync, statSync, readFileSync, existsSync } from "fs";
import { createReadStream } from 'fs'; import { createReadStream } from "fs";
import { join, resolve, extname, sep, relative } from 'path'; import { join, resolve, extname, sep, relative } from "path";
import { watch } from 'chokidar'; import { watch } from "chokidar";
interface ContentIndex { interface ContentIndex {
[path: string]: string; [path: string]: string;
@ -13,19 +13,19 @@ interface ContentIndex {
* MIME * MIME
*/ */
const MIME_TYPES: Record<string, string> = { const MIME_TYPES: Record<string, string> = {
'.html': 'text/html', ".html": "text/html",
'.css': 'text/css', ".css": "text/css",
'.js': 'application/javascript', ".js": "application/javascript",
'.json': 'application/json', ".json": "application/json",
'.png': 'image/png', ".png": "image/png",
'.jpg': 'image/jpeg', ".jpg": "image/jpeg",
'.jpeg': 'image/jpeg', ".jpeg": "image/jpeg",
'.gif': 'image/gif', ".gif": "image/gif",
'.svg': 'image/svg+xml', ".svg": "image/svg+xml",
'.md': 'text/markdown', ".md": "text/markdown",
'.csv': 'text/csv', ".csv": "text/csv",
'.woff': 'font/woff', ".woff": "font/woff",
'.woff2': 'font/woff2', ".woff2": "font/woff2",
}; };
/** /**
@ -33,11 +33,11 @@ const MIME_TYPES: Record<string, string> = {
*/ */
function getMimeType(filePath: string): string { function getMimeType(filePath: string): string {
const ext = extname(filePath).toLowerCase(); const ext = extname(filePath).toLowerCase();
return MIME_TYPES[ext] || 'application/octet-stream'; return MIME_TYPES[ext] || "application/octet-stream";
} }
/** /**
* .md .csv * .md
*/ */
function scanDirectory(dir: string): ContentIndex { function scanDirectory(dir: string): ContentIndex {
const index: ContentIndex = {}; const index: ContentIndex = {};
@ -46,18 +46,18 @@ function scanDirectory(dir: string): ContentIndex {
const entries = readdirSync(currentPath); const entries = readdirSync(currentPath);
for (const entry of entries) { for (const entry of entries) {
if (entry.startsWith('.') || entry === 'node_modules') continue; if (entry.startsWith(".") || entry === "node_modules") continue;
const fullPath = join(currentPath, entry); const fullPath = join(currentPath, entry);
const relPath = relativePath ? join(relativePath, entry) : entry; const relPath = relativePath ? join(relativePath, entry) : entry;
const normalizedRelPath = '/' + relPath.split(sep).join('/'); const normalizedRelPath = "/" + relPath.split(sep).join("/");
const stats = statSync(fullPath); const stats = statSync(fullPath);
if (stats.isDirectory()) { if (stats.isDirectory()) {
scan(fullPath, relPath); scan(fullPath, relPath);
} else if (entry.endsWith('.md') || entry.endsWith('.csv')) { } else if (entry.endsWith(".md")) {
try { try {
const content = readFileSync(fullPath, 'utf-8'); const content = readFileSync(fullPath, "utf-8");
index[normalizedRelPath] = content; index[normalizedRelPath] = content;
} catch (e) { } catch (e) {
console.error(`读取文件失败:${fullPath}`, e); console.error(`读取文件失败:${fullPath}`, e);
@ -66,7 +66,7 @@ function scanDirectory(dir: string): ContentIndex {
} }
} }
scan(dir, ''); scan(dir, "");
return index; return index;
} }
@ -75,8 +75,8 @@ function scanDirectory(dir: string): ContentIndex {
*/ */
function sendFile(res: ServerResponse, filePath: string) { function sendFile(res: ServerResponse, filePath: string) {
res.writeHead(200, { res.writeHead(200, {
'Content-Type': getMimeType(filePath), "Content-Type": getMimeType(filePath),
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
}); });
createReadStream(filePath).pipe(res); createReadStream(filePath).pipe(res);
} }
@ -85,8 +85,8 @@ function sendFile(res: ServerResponse, filePath: string) {
* 404 * 404
*/ */
function send404(res: ServerResponse) { function send404(res: ServerResponse) {
res.writeHead(404, { 'Content-Type': 'text/plain' }); res.writeHead(404, { "Content-Type": "text/plain" });
res.end('Not Found'); res.end("Not Found");
} }
/** /**
@ -94,8 +94,8 @@ function send404(res: ServerResponse) {
*/ */
function sendJson(res: ServerResponse, data: unknown) { function sendJson(res: ServerResponse, data: unknown) {
res.writeHead(200, { res.writeHead(200, {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
}); });
res.end(JSON.stringify(data, null, 2)); res.end(JSON.stringify(data, null, 2));
} }
@ -104,7 +104,11 @@ function sendJson(res: ServerResponse, data: unknown) {
* *
* @returns true * @returns true
*/ */
function tryServeStatic(res: ServerResponse, filePath: string, dir: string): boolean { function tryServeStatic(
res: ServerResponse,
filePath: string,
dir: string,
): boolean {
const fullPath = join(dir, filePath); const fullPath = join(dir, filePath);
if (!existsSync(fullPath)) { if (!existsSync(fullPath)) {
@ -129,17 +133,17 @@ function createRequestHandler(
getIndex: () => ContentIndex, getIndex: () => ContentIndex,
) { ) {
return (req: IncomingMessage, res: ServerResponse) => { return (req: IncomingMessage, res: ServerResponse) => {
const url = req.url || '/'; const url = req.url || "/";
const filePath = decodeURIComponent(url.split('?')[0]); const filePath = decodeURIComponent(url.split("?")[0]);
// 1. 处理 /__CONTENT_INDEX.json // 1. 处理 /__CONTENT_INDEX.json
if (filePath === '/__CONTENT_INDEX.json') { if (filePath === "/__CONTENT_INDEX.json") {
sendJson(res, getIndex()); sendJson(res, getIndex());
return; return;
} }
// 2. 处理 /static/ 目录(从 dist/web // 2. 处理 /static/ 目录(从 dist/web
if (filePath.startsWith('/static/')) { if (filePath.startsWith("/static/")) {
if (tryServeStatic(res, filePath, distDir)) { if (tryServeStatic(res, filePath, distDir)) {
return; return;
} }
@ -154,7 +158,7 @@ function createRequestHandler(
} }
// 4. 处理 SPA 路由:返回 index.html // 4. 处理 SPA 路由:返回 index.html
if (tryServeStatic(res, 'index.html', distDir)) { if (tryServeStatic(res, "index.html", distDir)) {
return; return;
} }
@ -168,16 +172,16 @@ function createRequestHandler(
*/ */
export const serveCommand: ServeCommandHandler = async (dir, options) => { export const serveCommand: ServeCommandHandler = async (dir, options) => {
const contentDir = resolve(dir); const contentDir = resolve(dir);
const distDir = resolve(process.cwd(), 'dist/web'); const distDir = resolve(process.cwd(), "dist/web");
let contentIndex: ContentIndex = {}; let contentIndex: ContentIndex = {};
// 扫描内容目录生成索引 // 扫描内容目录生成索引
console.log('扫描内容目录...'); console.log("扫描内容目录...");
contentIndex = scanDirectory(contentDir); contentIndex = scanDirectory(contentDir);
console.log(`已索引 ${Object.keys(contentIndex).length} 个文件`); console.log(`已索引 ${Object.keys(contentIndex).length} 个文件`);
// 监听文件变化 // 监听文件变化
console.log('监听文件变化...'); console.log("监听文件变化...");
const watcher = watch(contentDir, { const watcher = watch(contentDir, {
ignored: /(^|[\/\\])\../, ignored: /(^|[\/\\])\../,
persistent: true, persistent: true,
@ -185,11 +189,11 @@ export const serveCommand: ServeCommandHandler = async (dir, options) => {
}); });
watcher watcher
.on('add', (path) => { .on("add", (path) => {
if (path.endsWith('.md') || path.endsWith('.csv')) { if (path.endsWith(".md")) {
try { try {
const content = readFileSync(path, 'utf-8'); const content = readFileSync(path, "utf-8");
const relPath = '/' + relative(contentDir, path).split(sep).join('/'); const relPath = "/" + relative(contentDir, path).split(sep).join("/");
contentIndex[relPath] = content; contentIndex[relPath] = content;
console.log(`[新增] ${relPath}`); console.log(`[新增] ${relPath}`);
} catch (e) { } catch (e) {
@ -197,11 +201,11 @@ export const serveCommand: ServeCommandHandler = async (dir, options) => {
} }
} }
}) })
.on('change', (path) => { .on("change", (path) => {
if (path.endsWith('.md') || path.endsWith('.csv')) { if (path.endsWith(".md")) {
try { try {
const content = readFileSync(path, 'utf-8'); const content = readFileSync(path, "utf-8");
const relPath = '/' + relative(contentDir, path).split(sep).join('/'); const relPath = "/" + relative(contentDir, path).split(sep).join("/");
contentIndex[relPath] = content; contentIndex[relPath] = content;
console.log(`[更新] ${relPath}`); console.log(`[更新] ${relPath}`);
} catch (e) { } catch (e) {
@ -209,9 +213,9 @@ export const serveCommand: ServeCommandHandler = async (dir, options) => {
} }
} }
}) })
.on('unlink', (path) => { .on("unlink", (path) => {
if (path.endsWith('.md') || path.endsWith('.csv')) { if (path.endsWith(".md")) {
const relPath = '/' + relative(contentDir, path).split(sep).join('/'); const relPath = "/" + relative(contentDir, path).split(sep).join("/");
delete contentIndex[relPath]; delete contentIndex[relPath];
console.log(`[删除] ${relPath}`); console.log(`[删除] ${relPath}`);
} }

View File

@ -23,7 +23,7 @@ async function loadDevIndex(): Promise<void> {
if (typeof import.meta !== "undefined" && import.meta.glob) { if (typeof import.meta !== "undefined" && import.meta.glob) {
try { try {
// @ts-ignore - 只加载 .csv 和 .md 文件 // @ts-ignore - 只加载 .csv 和 .md 文件
const modules = import.meta.glob("../../content/**/*.{csv,md}", { const modules = import.meta.glob("../../content/**/*.md", {
as: "raw", as: "raw",
eager: true, eager: true,
}); });