修复 temp-cleaner 在 LLRT 上的 unlink 兼容性问题

- 修改 interface.ts: 接受 options.unlink 和 options.rm 作为 FS 适配器的可配置方法
- 修改 llrt.ts: 使用箭头函数 (path) => fs.rm(path) 避免 this 绑定问题
- 修改 app.ts: 通过 __RUNTIME__ 条件编译,仅在 LLRT 运行时引入 llrt.ts
- 修改 tsdown.config.ts: format 从字符串数组改为字符串类型,添加 as const
- 修改 tsconfig.node.json: 添加 node 类型支持
- 新增 backend/global.d.ts: 声明 __RUNTIME__ 全局变量类型
- 修改 .gitignore: 添加 dist_backend_node 目录
- 修改 build-backend.ts: 支持 Node.js 版本构建输出到 dist_backend_node

修复问题:
- LLRT fs/promises 没有 unlink 方法,只有 rm 方法
- CJS 格式不支持 top-level await,改用 IIFE + require
- 条件编译避免 Node.js 版本包含不必要的 LLRT 适配代码
This commit is contained in:
崮生(子虚) 2026-08-15 07:02:22 +08:00
parent d16aa46227
commit dc1ee66fe3
8 changed files with 75 additions and 11 deletions

1
.gitignore vendored
View File

@ -30,6 +30,7 @@ app
*.tar
dist_backend
dist_backend_node
.pilot
verify_font_baseline

View File

@ -1,3 +1,10 @@
/** 首先加载 fs 适配层,必须在所有其他导入之前!仅在 LLRT 运行时引入 */
(() => {
if (typeof __RUNTIME__ !== "undefined" && __RUNTIME__ === "llrt") {
require("./server/llrt");
}
})();
import { mimeTypes } from "./server/mime_type";
import type { cMiddleware } from "./server/req_res";
import { SimpleHttpServer } from "./server/server";
@ -15,8 +22,6 @@ import { handleFontMeta } from "./routes/font_meta";
import { startTempCleaner } from "./temp_cleaner";
import { initMemoryGate } from "./subset_queue";
import { subsetConcurrency } from "./config";
import "./server/node";
import "./server/llrt";
const ROOT_DIR = "dist";

2
backend/global.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/** 构建时由 tsdown 注入的运行时标识 */
declare const __RUNTIME__: "llrt" | "node";

View File

@ -18,20 +18,34 @@ export let mkdir: (path: string) => Promise<void>;
export let unlink: (path: string) => Promise<void>;
/** LLRT 专用:保存 rm 函数引用,避免闭包问题 */
let llrtRm: ((path: string) => Promise<void>) | undefined;
export const implInterface = (options: {
stat: typeof stat;
readFile: typeof readFile;
writeFile: typeof writeFile;
readdir: typeof readdir;
mkdir: typeof mkdir;
unlink: typeof unlink;
unlink?: typeof unlink;
/** LLRT 没有 unlink提供 rm 作为替代 */
rm?: (path: string) => Promise<void>;
}) => {
stat = options.stat;
readFile = options.readFile;
writeFile = options.writeFile;
readdir = options.readdir;
mkdir = options.mkdir;
unlink = options.unlink;
// 保存 rm 引用到模块级变量
llrtRm = options.rm;
/** LLRT 的 fs/promises 没有 unlink需要用 rm 代替 */
unlink = async (path) => {
if (options.unlink) {
await options.unlink(path);
} else if (llrtRm) {
await llrtRm(path);
}
};
};
export function path_join(...paths: string[]) {

View File

@ -1 +1,27 @@
import "web-streams-polyfill/polyfill";
import { implInterface } from "../interface";
import * as fs from "fs/promises";
/**
* LLRT fs
*
* LLRT fs.readdir { name, isFile }
* LLRT fs/promises unlink implInterface rm
*/
implInterface({
stat: fs.stat,
readFile: fs.readFile,
writeFile: fs.writeFile,
async readdir(path) {
const names = await fs.readdir(path);
return Promise.all(
names.map(async (name) => {
const s = await fs.stat(`${path}/${name}`);
return { name, isFile: () => s.isFile() };
})
);
},
mkdir: (path) => fs.mkdir(path, { recursive: true }),
/** LLRT 没有 unlink用箭头函数包装 rm 避免 this 问题 */
rm: (path: string) => fs.rm(path),
});

View File

@ -102,9 +102,9 @@ function runTsdown() {
/** woff2 已使用纯 JS 实现vendor/fonteditor-core/woff2/index.js无需复制 wasm */
/** 使用 LLRT compile 生成 .lrt 文件 */
/** 使用 LLRT compile 生成 .lrt 文件(仅 LLRT 构建需要) */
function runLlrtCompile() {
console.log("\n--- Running LLRT compile ---");
console.log("\n--- Running LLRT compile (LLRT only) ---");
execSync(`${LLRT_BIN} compile ./dist_backend/app.cjs ./dist_backend/app.lrt`, {
stdio: "inherit",
cwd: ROOT_DIR,

View File

@ -16,7 +16,8 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"types": ["node"]
},
"include": [
"vite.config.ts",

View File

@ -5,12 +5,10 @@ import { readFileSync } from "node:fs";
const packageVersion = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version;
const shared = {
format: ["cjs"],
format: "cjs",
clean: true,
sourcemap: true,
outDir: "dist_backend",
/** 构建期把 PACKAGE_VERSION 替换为字面量字符串,运行时无任何文件读取 */
define: { PACKAGE_VERSION: JSON.stringify(packageVersion) },
outputOptions: {
/** 禁用代码拆分确保单文件输出tsdown 默认会拆分大依赖为 chunk */
codeSplitting: false,
@ -19,13 +17,30 @@ const shared = {
/** 所有依赖都打进 bundleLLRT scratch 镜像无 node_modules */
alwaysBundle: [/.*/],
},
};
} as const;
export default [
defineConfig({
...shared,
/** LLRT 构建 */
define: {
PACKAGE_VERSION: JSON.stringify(packageVersion),
__RUNTIME__: JSON.stringify("llrt"),
},
entry: ["backend/app.ts"],
}),
defineConfig({
...shared,
/** Node.js 构建(用于本地开发测试) */
define: {
PACKAGE_VERSION: JSON.stringify(packageVersion),
__RUNTIME__: JSON.stringify("node"),
},
/** 第二个配置不 clean避免清掉第一个的输出 */
clean: false,
entry: ["backend/app.ts"],
outDir: "dist_backend_node",
}),
defineConfig({
...shared,
/** 第二个配置不 clean避免清掉第一个的输出 */