Merge pull request '修复 AGC 开发态监听 Rust 构建目录导致的加载缓慢' (#382) from fix/agc-vite-target-watch into master
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Reviewed-on: #382
This commit was merged in pull request #382.
This commit is contained in:
@@ -0,0 +1,113 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { test } from 'node:test';
|
||||||
|
import { setTimeout as delay } from 'node:timers/promises';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import { createServer, loadConfigFromFile, normalizePath } from 'vite';
|
||||||
|
|
||||||
|
test(
|
||||||
|
'AGC 排除 Rust 构建目录且保留源码与共享组件监听',
|
||||||
|
{ timeout: 30_000 },
|
||||||
|
async () => {
|
||||||
|
const loaded = await loadConfigFromFile(
|
||||||
|
{ command: 'serve', mode: 'development' },
|
||||||
|
fileURLToPath(new URL('../vite.config.ts', import.meta.url)),
|
||||||
|
);
|
||||||
|
assert.ok(loaded);
|
||||||
|
assert.notEqual(loaded.config.server?.watch, null);
|
||||||
|
assert.notEqual(loaded.config.server?.hmr, false);
|
||||||
|
assert.ok(
|
||||||
|
[loaded.config.server?.watch?.ignored]
|
||||||
|
.flat()
|
||||||
|
.includes('**/src-tauri/target/**'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const fixture = await mkdtemp(join(tmpdir(), 'agc-vite-watch-'));
|
||||||
|
const root = join(fixture, 'apps', 'ai-game-creator-shell');
|
||||||
|
const source = join(root, 'src', 'main.js');
|
||||||
|
const css = join(root, 'src', 'styles.css');
|
||||||
|
const shared = join(fixture, 'packages', 'shared', 'src', 'component.js');
|
||||||
|
const target = join(root, 'src-tauri', 'target');
|
||||||
|
const artifact = join(target, 'debug', 'incremental', 'cache.bin');
|
||||||
|
let server;
|
||||||
|
try {
|
||||||
|
for (const file of [source, css, shared, artifact]) {
|
||||||
|
await mkdir(dirname(file), { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
file,
|
||||||
|
file === css ? 'body { color: red; }' : 'export default 1;',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 使用真实 Vite watcher 和实际配置,仅将扫描根替换为小型夹具;
|
||||||
|
// 不加载业务插件、后端或原生窗口,也不扫描开发机上的大型 target。
|
||||||
|
server = await createServer({
|
||||||
|
configFile: false,
|
||||||
|
envFile: false,
|
||||||
|
root,
|
||||||
|
logLevel: 'silent',
|
||||||
|
server: {
|
||||||
|
watch: loaded.config.server?.watch,
|
||||||
|
middlewareMode: true,
|
||||||
|
hmr: false,
|
||||||
|
fs: { allow: [fixture] },
|
||||||
|
},
|
||||||
|
optimizeDeps: { noDiscovery: true, include: [] },
|
||||||
|
});
|
||||||
|
const waitForWatchedFile = async (file) => {
|
||||||
|
const normalized = normalizePath(file);
|
||||||
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
||||||
|
if (
|
||||||
|
Object.entries(server.watcher.getWatched()).some(
|
||||||
|
([directory, names]) =>
|
||||||
|
names.some(
|
||||||
|
(name) => normalizePath(join(directory, name)) === normalized,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
await delay(50);
|
||||||
|
}
|
||||||
|
assert.fail(`源码必须仍被监听:${normalized}`);
|
||||||
|
};
|
||||||
|
await waitForWatchedFile(source);
|
||||||
|
|
||||||
|
// 真实模块转换应将 root 外的共享源码加入监听。
|
||||||
|
await server.transformRequest(`/@fs/${normalizePath(shared)}`);
|
||||||
|
for (const file of [source, css, shared]) {
|
||||||
|
const normalized = normalizePath(file);
|
||||||
|
await waitForWatchedFile(file);
|
||||||
|
const changed = new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
server.watcher.off('change', onChange);
|
||||||
|
reject(new Error(`未收到源码变更:${normalized}`));
|
||||||
|
}, 5_000);
|
||||||
|
function onChange(path) {
|
||||||
|
if (normalizePath(path) !== normalized) return;
|
||||||
|
clearTimeout(timer);
|
||||||
|
server.watcher.off('change', onChange);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
server.watcher.on('change', onChange);
|
||||||
|
});
|
||||||
|
await writeFile(
|
||||||
|
file,
|
||||||
|
file === css ? 'body { color: blue; }' : 'export default 2;',
|
||||||
|
);
|
||||||
|
await changed;
|
||||||
|
}
|
||||||
|
const targetPath = normalizePath(target);
|
||||||
|
const targetDirectories = Object.keys(server.watcher.getWatched())
|
||||||
|
.map(normalizePath)
|
||||||
|
.filter(
|
||||||
|
(path) => path === targetPath || path.startsWith(`${targetPath}/`),
|
||||||
|
);
|
||||||
|
assert.deepEqual(targetDirectories, [], 'Rust target 不应创建目录监听器');
|
||||||
|
} finally {
|
||||||
|
await server?.close();
|
||||||
|
await rm(fixture, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -154,6 +154,9 @@ export default defineConfig({
|
|||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: 3080,
|
port: 3080,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
|
watch: {
|
||||||
|
ignored: ['**/src-tauri/target/**'],
|
||||||
|
},
|
||||||
fs: {
|
fs: {
|
||||||
allow: [repoRoot],
|
allow: [repoRoot],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 踩坑与排障记录
|
# 踩坑与排障记录
|
||||||
|
|
||||||
|
## AGC Windows 开发态首次页面加载缓慢
|
||||||
|
|
||||||
|
Vite 默认监听应用根下的 Rust `src-tauri/target`,构建产物较多时会创建大量 Windows 文件监听器。AGC 配置通过 `server.watch.ignored: ['**/src-tauri/target/**']` 排除此目录,不关闭业务源码、CSS、共享组件监听或 HMR。排查时区分后端就绪、Vite 扫描和原生窗口首绘;监听目录回归不能代替实机首绘测量,验证入口见本地开发运维文档。
|
||||||
|
|
||||||
## 2026-09-17 AGC 输入盒的「推理档」弹层被祖先裁切:要放开裁切而不是挪弹层
|
## 2026-09-17 AGC 输入盒的「推理档」弹层被祖先裁切:要放开裁切而不是挪弹层
|
||||||
|
|
||||||
- **现象**:窄窗口下(视口 ≤1000px 时右侧对话面板只有 280px 宽)点开输入盒右下角的「推理档」,弹层是个**空盒子**:档位文字(默认 / 低 / 中 / 高 / 最高)整片看不见,只剩一个方框。
|
- **现象**:窄窗口下(视口 ≤1000px 时右侧对话面板只有 280px 宽)点开输入盒右下角的「推理档」,弹层是个**空盒子**:档位文字(默认 / 低 / 中 / 高 / 最高)整片看不见,只剩一个方框。
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ Stdb 发布以 root 准备文件、再切换 `spacetimedb` 用户执行时,WAS
|
|||||||
|
|
||||||
## 本地启动
|
## 本地启动
|
||||||
|
|
||||||
|
AGC Vite 的 `server.watch.ignored` 排除 `**/src-tauri/target/**`,避免递归监听 Rust 构建产物、在 Windows 上创建大量文件监听器并拖慢首次页面加载。保留业务源码、CSS 与仓库共享组件的监听及热更新;不通过关闭 watcher 或 HMR 规避问题。监听回归使用 `node --test apps/ai-game-creator-shell/scripts/vite-watch.test.mjs`,验证构建目录被排除、应用源码及根目录外的共享源码仍能触发变更;原生窗口首绘耗时另行实测,不把监听测试耗时当作启动性能指标。
|
||||||
|
|
||||||
AGC `backend` 模式与 `all` / `api-server` 一样,必须同时探测 API 和 BgFilter worker 端口,漂移后的 worker 地址同时传给 API、worker 和 readiness 检查。不能因为旧 worker 的 `/readyz` 可访问,就把新启动失败的同端口 worker 视为就绪;AGC 前端会等待完整配套后端,worker 失败可能最终表现为 Tauri 等待前端 180 秒超时。
|
AGC `backend` 模式与 `all` / `api-server` 一样,必须同时探测 API 和 BgFilter worker 端口,漂移后的 worker 地址同时传给 API、worker 和 readiness 检查。不能因为旧 worker 的 `/readyz` 可访问,就把新启动失败的同端口 worker 视为就绪;AGC 前端会等待完整配套后端,worker 失败可能最终表现为 Tauri 等待前端 180 秒超时。
|
||||||
|
|
||||||
`npm run agc` 外层启动器先执行 `agc:serve` 并等待前端与配套后端就绪,再启动 Tauri,同时清空本次 CLI 的 `beforeDevCommand`,避免重复拉起服务和把数据库发布时间计入 Tauri 的 180 秒前端等待。准备阶段最多等待 660 秒(后端门禁仍为 600 秒),退出时清理本次启动的服务树,不停止复用的服务。AGC 自动发布显式使用 `--preserve-database`,schema 冲突须人工确认迁移,不自动清空数据。
|
`npm run agc` 外层启动器先执行 `agc:serve` 并等待前端与配套后端就绪,再启动 Tauri,同时清空本次 CLI 的 `beforeDevCommand`,避免重复拉起服务和把数据库发布时间计入 Tauri 的 180 秒前端等待。准备阶段最多等待 660 秒(后端门禁仍为 600 秒),退出时清理本次启动的服务树,不停止复用的服务。AGC 自动发布显式使用 `--preserve-database`,schema 冲突须人工确认迁移,不自动清空数据。
|
||||||
|
|||||||
Reference in New Issue
Block a user