优化开发体验

This commit is contained in:
崮生(子虚) 2026-04-08 15:35:11 +08:00
parent 6a5f0a72c4
commit e5b6718ad9
5 changed files with 177 additions and 2 deletions

View File

@ -4,9 +4,11 @@
"version": "1.0.4",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "pnpx tsx scripts/dev-all.ts",
"dev:frontend": "vite",
"dev:backend": "pnpx tsx backend/app.ts",
"build": "vite build",
"build_backend": "tsup && ./llrt compile ./dist_backend/app.cjs ./dist_backend/app.lrt",
"build_backend": "pnpx tsx scripts/build-backend.ts",
"docker_build": "docker build -t llej0/web-font:${npm_package_version} -t llej0/web-font:latest .",
"docker_push": "docker push llej0/web-font:${npm_package_version} && docker push llej0/web-font:latest",
"preview": "vite preview",

114
scripts/build-backend.ts Normal file
View File

@ -0,0 +1,114 @@
/**
*
* 1. LLRT
* 2. tsup
* 3. 使 LLRT compile .lrt
*/
import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { mkdir, writeFile, rm } from "node:fs/promises";
import { arch, platform } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT_DIR = join(__dirname, "..");
const LLRT_BIN = join(ROOT_DIR, "llrt");
/** 映射 node arch 到 LLRT arch */
function getLlrtArch() {
const a = arch();
if (a === "x64") return "x64";
if (a === "arm64") return "arm64";
throw new Error(`Unsupported architecture: ${a}`);
}
/** 映射 node platform 到 LLRT platform */
function getLlrtPlatform() {
const p = platform();
if (p === "linux") return "linux";
if (p === "darwin") return "darwin";
if (p === "win32") return "windows";
throw new Error(`Unsupported platform: ${p}`);
}
/** 确保 llrt 二进制文件存在,不存在则下载 */
async function ensureLlrt() {
if (existsSync(LLRT_BIN)) {
console.log("LLRT binary found, skipping download.");
return;
}
const llrtArch = getLlrtArch();
const llrtPlatform = getLlrtPlatform();
console.log("Fetching latest LLRT release version...");
const res = await fetch("https://api.github.com/repos/awslabs/llrt/releases/latest");
/** 响应数据 */
const data = await res.json() as { tag_name: string };
const version = data.tag_name;
if (!version) {
throw new Error("Failed to fetch latest LLRT version from GitHub");
}
console.log(`Latest LLRT version: ${version}`);
const downloadUrl = `https://github.com/awslabs/llrt/releases/download/${version}/llrt-${llrtPlatform}-${llrtArch}.zip`;
console.log(`Downloading from ${downloadUrl} ...`);
const zipRes = await fetch(downloadUrl);
if (!zipRes.ok) {
throw new Error(`Failed to download LLRT: ${zipRes.status} ${zipRes.statusText}`);
}
/** 下载 zip 到临时文件 */
const tmpDir = join(ROOT_DIR, ".tmp-llrt");
await mkdir(tmpDir, { recursive: true });
const zipPath = join(tmpDir, "llrt.zip");
const arrayBuffer = await zipRes.arrayBuffer();
await writeFile(zipPath, Buffer.from(arrayBuffer));
/** 使用 unzip 命令解压Node 内置没有 zip 解压) */
const binaryName = platform() === "win32" ? "llrt.exe" : "llrt";
execSync(`unzip -o -j "${zipPath}" "${binaryName}" -d "${ROOT_DIR}"`, {
stdio: "inherit",
});
/** linux/mac 需要可执行权限 */
if (platform() !== "win32") {
execSync(`chmod +x "${LLRT_BIN}"`);
}
await rm(tmpDir, { recursive: true });
console.log(`LLRT ${version} installed successfully.`);
}
/** 运行 tsup 编译 */
function runTsup() {
console.log("\n--- Running tsup build ---");
execSync("pnpm tsup", { stdio: "inherit", cwd: ROOT_DIR });
}
/** 使用 LLRT compile 生成 .lrt 文件 */
function runLlrtCompile() {
console.log("\n--- Running LLRT compile ---");
execSync(`${LLRT_BIN} compile ./dist_backend/app.cjs ./dist_backend/app.lrt`, {
stdio: "inherit",
cwd: ROOT_DIR,
});
console.log("\nBackend build completed successfully!");
}
/** 主流程 */
async function main() {
await ensureLlrt();
runTsup();
runLlrtCompile();
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

45
scripts/dev-all.ts Normal file
View File

@ -0,0 +1,45 @@
/**
* (vite) (tsx backend/app.ts)
* Ctrl+C
*/
import { spawn } from "node:child_process";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT_DIR = join(__dirname, "..");
/** 子进程列表,用于退出时统一清理 */
const children: ReturnType<typeof spawn>[] = [];
/** 清理所有子进程 */
function cleanup() {
for (const child of children) {
child.kill("SIGTERM");
}
}
process.on("SIGINT", () => {
cleanup();
process.exit(0);
});
process.on("SIGTERM", () => {
cleanup();
process.exit(0);
});
console.log("Starting frontend and backend dev servers...\n");
const backend = spawn("pnpx", ["tsx", "backend/app.ts"], {
cwd: ROOT_DIR,
stdio: "inherit",
shell: true,
});
children.push(backend);
const frontend = spawn("pnpm", ["dev:frontend"], {
cwd: ROOT_DIR,
stdio: "inherit",
shell: true,
});
children.push(frontend);

View File

@ -11,6 +11,9 @@
},
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.scripts.json"
}
]
}

11
tsconfig.scripts.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["node"],
"esModuleInterop": true,
"strict": true
},
"include": ["scripts"]
}