- 用 pnpm 替换 npm,锁定 pnpm@10.33.0,删除 package-lock.json 相关说明 - package.json 将渲染层依赖移入 devDependencies,减小 app.asar 体积 - 新增 electron:smoke 冒烟脚本与 icon 生成脚本 - win 平台设置 AppUserModelID,修复任务栏图标归组 - 清理 tsBuildInfoFile 输出路径至 node_modules/.tmp - 更新测试断言以匹配画幅遮罩、取景器缩放与模型库外部资产行为 - README 与打包指南补充 pnpm、镜像与常见问题说明
106 lines
3.9 KiB
JavaScript
106 lines
3.9 KiB
JavaScript
// scripts/test-electron.cjs
|
||
// 冒烟测试:确认构建产物 dist/index.html 能被 Electron 正常加载,且渲染层没有报错。
|
||
//
|
||
// 用法:pnpm run electron:smoke
|
||
// 注意:package.json 中有 "type": "module",因此本文件必须使用 .cjs 后缀,
|
||
// 否则 require 会因被当作 ESM 而报错。
|
||
//
|
||
// 这个脚本也用来验证「dist 是自包含的」:渲染层需要的所有依赖都已被 Vite
|
||
// 打包进 dist/assets,因此打包时不需要把 node_modules 塞进 app.asar。
|
||
|
||
// 有的终端 / CI 会注入 ELECTRON_RUN_AS_NODE=1,那会让 Electron 退化成纯 Node,
|
||
// require('electron') 只返回二进制路径,后面会以 "Cannot read properties of
|
||
// undefined (reading 'whenReady')" 收场。提前拦下并给出可执行的修复命令。
|
||
if (process.env.ELECTRON_RUN_AS_NODE) {
|
||
console.error("FAIL 检测到环境变量 ELECTRON_RUN_AS_NODE 已设置。");
|
||
console.error(" Electron 会因此退化成纯 Node,require('electron') 只返回二进制路径。");
|
||
console.error(" 清掉它再运行:");
|
||
console.error(" bash : unset ELECTRON_RUN_AS_NODE");
|
||
console.error(" cmd : set ELECTRON_RUN_AS_NODE=");
|
||
console.error(" PowerShell: Remove-Item Env:ELECTRON_RUN_AS_NODE");
|
||
process.exit(1);
|
||
}
|
||
|
||
const { app, BrowserWindow } = require("electron");
|
||
const path = require("path");
|
||
|
||
console.log("Electron:", process.versions.electron);
|
||
console.log("Chrome:", process.versions.chrome);
|
||
console.log("Node:", process.versions.node);
|
||
|
||
const distEntry = path.join(__dirname, "..", "dist", "index.html");
|
||
|
||
const rendererErrors = [];
|
||
const IGNORABLE = [
|
||
// 模型库是仓库外的外部资产,缺失时浏览器只会报资源 404,不影响应用本身
|
||
/模型库/,
|
||
/net::ERR_FILE_NOT_FOUND/,
|
||
];
|
||
|
||
// 加载 + 渲染 + 首帧动画都跑完,再判定结果
|
||
const SETTLE_MS = 4000;
|
||
|
||
app.whenReady().then(async () => {
|
||
const mainWindow = new BrowserWindow({
|
||
width: 800,
|
||
height: 600,
|
||
show: false,
|
||
webPreferences: {
|
||
// 与 electron/main.cjs 保持一致,避免冒烟环境比生产环境宽松
|
||
nodeIntegration: false,
|
||
contextIsolation: true,
|
||
},
|
||
});
|
||
|
||
mainWindow.webContents.on("console-message", (_event, level, message) => {
|
||
// level: 0=verbose 1=info 2=warning 3=error
|
||
if (level >= 3 && !IGNORABLE.some((re) => re.test(message))) {
|
||
rendererErrors.push(message);
|
||
}
|
||
});
|
||
|
||
mainWindow.webContents.on("did-fail-load", (_event, code, description, url) => {
|
||
rendererErrors.push(`did-fail-load ${code} ${description} ${url}`);
|
||
});
|
||
|
||
mainWindow.webContents.on("render-process-gone", (_event, details) => {
|
||
rendererErrors.push(`render-process-gone ${details.reason}`);
|
||
});
|
||
|
||
try {
|
||
await mainWindow.loadFile(distEntry);
|
||
console.log("OK 页面加载成功:", distEntry);
|
||
} catch (error) {
|
||
console.error("FAIL 页面加载失败:", error.message);
|
||
console.error(" 请先执行 pnpm run build 生成 dist/");
|
||
app.exit(1);
|
||
return;
|
||
}
|
||
|
||
// 给 React 首帧和 R3F 初始化留出时间,暴露运行时错误
|
||
await new Promise((resolve) => setTimeout(resolve, SETTLE_MS));
|
||
|
||
const rootChildCount = await mainWindow.webContents
|
||
.executeJavaScript("document.getElementById('root')?.childElementCount ?? -1")
|
||
.catch(() => -1);
|
||
|
||
if (rootChildCount > 0) {
|
||
console.log(`OK React 已挂载(#root 子节点 ${rootChildCount} 个)`);
|
||
} else {
|
||
rendererErrors.push(`#root 未渲染任何子节点(childElementCount=${rootChildCount})`);
|
||
}
|
||
|
||
if (rendererErrors.length === 0) {
|
||
console.log("PASS 冒烟测试通过:页面加载、React 挂载、无渲染层错误");
|
||
app.exit(0);
|
||
} else {
|
||
console.error(`FAIL 渲染层出现 ${rendererErrors.length} 个错误:`);
|
||
rendererErrors.slice(0, 10).forEach((message) => console.error(" -", message));
|
||
app.exit(1);
|
||
}
|
||
});
|
||
|
||
app.on("window-all-closed", () => {
|
||
app.quit();
|
||
});
|