mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-22 17:00:21 +08:00
⚡️ perf(dev): 优化 Wails 开发启动与 CI 构建耗时
- 新增 Wails 快速开发启动脚本,跳过非必要构建与绑定生成 - 优化前端依赖安装状态判断,减少重复 npm install - 固定 CI Wails CLI 版本并增加 node_modules 缓存 - 更新开发文档中的快速启动说明
This commit is contained in:
9
.github/workflows/dev-build.yml
vendored
9
.github/workflows/dev-build.yml
vendored
@@ -86,7 +86,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
check-latest: true
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -95,6 +94,12 @@ jobs:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Cache frontend node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: frontend/node_modules
|
||||
key: ${{ runner.os }}-node20-frontend-${{ hashFiles('frontend/package-lock.json') }}
|
||||
|
||||
- name: Install UPX (Windows)
|
||||
if: contains(matrix.platform, 'windows')
|
||||
shell: pwsh
|
||||
@@ -155,7 +160,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Install Wails
|
||||
run: go install -v github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||
run: go install -v github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
|
||||
|
||||
- name: Setup MSYS2 Toolchain For DuckDB (Windows AMD64)
|
||||
id: msys2_duckdb
|
||||
|
||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -84,7 +84,6 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
check-latest: true
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -93,6 +92,12 @@ jobs:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Cache frontend node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: frontend/node_modules
|
||||
key: ${{ runner.os }}-node20-frontend-${{ hashFiles('frontend/package-lock.json') }}
|
||||
|
||||
- name: Install UPX (Windows)
|
||||
if: contains(matrix.platform, 'windows')
|
||||
shell: pwsh
|
||||
@@ -157,7 +162,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Install Wails
|
||||
run: go install -v github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||
run: go install -v github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
|
||||
|
||||
- name: Setup MSYS2 Toolchain For DuckDB (Windows AMD64)
|
||||
id: msys2_duckdb
|
||||
|
||||
@@ -133,7 +133,7 @@ GoNavi is designed for developers and DBAs who need a unified desktop experience
|
||||
- [Go](https://go.dev/dl/) 1.21+
|
||||
- [Node.js](https://nodejs.org/) 18+
|
||||
- [Wails CLI](https://wails.io/docs/gettingstarted/installation):
|
||||
`go install github.com/wailsapp/wails/v2/cmd/wails@latest`
|
||||
`go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0`
|
||||
|
||||
### Development Mode
|
||||
|
||||
@@ -144,6 +144,12 @@ cd GoNavi
|
||||
|
||||
# Start development with hot reload
|
||||
wails dev
|
||||
|
||||
# Faster local startup when exported Go method signatures are unchanged
|
||||
node tools/wails-fast-dev.mjs
|
||||
|
||||
# Refresh Wails JS bindings after changing exported Go method signatures
|
||||
node tools/wails-fast-dev.mjs --refresh-bindings
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
@@ -127,7 +127,7 @@ GoNavi 面向开发者与 DBA,核心目标是让数据库操作在桌面端做
|
||||
- [Go](https://go.dev/dl/) 1.21+
|
||||
- [Node.js](https://nodejs.org/) 18+
|
||||
- [Wails CLI](https://wails.io/docs/gettingstarted/installation):
|
||||
`go install github.com/wailsapp/wails/v2/cmd/wails@latest`
|
||||
`go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0`
|
||||
|
||||
### 开发模式
|
||||
|
||||
@@ -138,6 +138,12 @@ cd GoNavi
|
||||
|
||||
# 启动开发(热重载)
|
||||
wails dev
|
||||
|
||||
# 本地快速启动:未修改 Go 导出方法签名时使用
|
||||
node tools/wails-fast-dev.mjs
|
||||
|
||||
# 修改 Go 导出方法签名后刷新 Wails JS 绑定
|
||||
node tools/wails-fast-dev.mjs --refresh-bindings
|
||||
```
|
||||
|
||||
### 编译构建
|
||||
|
||||
@@ -1 +1 @@
|
||||
0295a42fd931778d85157816d79d29e5
|
||||
d0464f9da25e9356e61652e638c99ffe
|
||||
95
frontend/scripts/wails-frontend-install.mjs
Normal file
95
frontend/scripts/wails-frontend-install.mjs
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const frontendDir = process.cwd();
|
||||
const packageJsonPath = path.join(frontendDir, 'package.json');
|
||||
const packageLockPath = path.join(frontendDir, 'package-lock.json');
|
||||
const nodeModulesPath = path.join(frontendDir, 'node_modules');
|
||||
const npmHiddenLockPath = path.join(nodeModulesPath, '.package-lock.json');
|
||||
const installStatePath = path.join(nodeModulesPath, '.gonavi-install-state.json');
|
||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
const commonArgs = [
|
||||
'--prefer-offline',
|
||||
'--no-audit',
|
||||
'--fund=false',
|
||||
'--fetch-retries=5',
|
||||
'--fetch-retry-mintimeout=20000',
|
||||
'--fetch-retry-maxtimeout=120000',
|
||||
];
|
||||
|
||||
const hashFile = (filePath) => {
|
||||
const hash = createHash('sha256');
|
||||
hash.update(readFileSync(filePath));
|
||||
return hash.digest('hex');
|
||||
};
|
||||
|
||||
const currentState = () => ({
|
||||
packageJson: hashFile(packageJsonPath),
|
||||
packageLock: existsSync(packageLockPath) ? hashFile(packageLockPath) : '',
|
||||
});
|
||||
|
||||
const readInstalledState = () => {
|
||||
if (!existsSync(installStatePath)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(installStatePath, 'utf8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const writeInstalledState = (state) => {
|
||||
writeFileSync(installStatePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
||||
};
|
||||
|
||||
const packageInputsAreOlderThanNpmLock = () => {
|
||||
if (!existsSync(npmHiddenLockPath)) return false;
|
||||
const markerTime = statSync(npmHiddenLockPath).mtimeMs;
|
||||
return [packageJsonPath, packageLockPath]
|
||||
.filter(existsSync)
|
||||
.every((filePath) => statSync(filePath).mtimeMs <= markerTime);
|
||||
};
|
||||
|
||||
const runNpm = (subcommand) => {
|
||||
const result = spawnSync(npmCommand, [subcommand, ...commonArgs], {
|
||||
cwd: frontendDir,
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
};
|
||||
|
||||
const state = currentState();
|
||||
const installedState = readInstalledState();
|
||||
const forceInstall = process.env.GONAVI_FORCE_FRONTEND_INSTALL === '1';
|
||||
const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
|
||||
|
||||
if (!forceInstall && existsSync(nodeModulesPath)) {
|
||||
if (
|
||||
installedState?.packageJson === state.packageJson &&
|
||||
installedState?.packageLock === state.packageLock
|
||||
) {
|
||||
console.log('Frontend dependencies are up to date; skipping npm install.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!installedState && isCI && existsSync(npmHiddenLockPath)) {
|
||||
writeInstalledState(state);
|
||||
console.log('Frontend dependencies are up to date from CI cache; recorded install state.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!installedState && packageInputsAreOlderThanNpmLock()) {
|
||||
writeInstalledState(state);
|
||||
console.log('Frontend dependencies are up to date; recorded install state.');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
runNpm(isCI ? 'ci' : 'install');
|
||||
writeInstalledState(state);
|
||||
115
tools/wails-fast-dev.mjs
Normal file
115
tools/wails-fast-dev.mjs
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = path.resolve(scriptDir, '..');
|
||||
const frontendDir = path.join(projectRoot, 'frontend');
|
||||
const wailsConfigPath = path.join(projectRoot, 'wails.json');
|
||||
const nodeCommand = process.execPath;
|
||||
const wailsCommand = process.platform === 'win32' ? 'wails.exe' : 'wails';
|
||||
|
||||
const usage = `Usage:
|
||||
node tools/wails-fast-dev.mjs [--refresh-bindings] [--no-install] [--dry-run] [wails dev flags...]
|
||||
|
||||
Fast path:
|
||||
- skips npm install when frontend dependencies are unchanged
|
||||
- runs wails dev with -m -s -nosyncgomod -skipembedcreate
|
||||
- skips Wails binding generation unless --refresh-bindings is passed
|
||||
|
||||
Use --refresh-bindings after changing exported Go method signatures.`;
|
||||
|
||||
const rawArgs = process.argv.slice(2);
|
||||
if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const readWailsConfig = () => {
|
||||
try {
|
||||
return JSON.parse(readFileSync(wailsConfigPath, 'utf8'));
|
||||
} catch (error) {
|
||||
console.error(`Failed to read wails.json: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const runFrontendInstall = () => {
|
||||
const result = spawnSync(nodeCommand, ['scripts/wails-frontend-install.mjs'], {
|
||||
cwd: frontendDir,
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
};
|
||||
|
||||
const hasFlag = (args, names) =>
|
||||
args.some((arg) => names.some((name) => arg === name || arg.startsWith(`${name}=`)));
|
||||
|
||||
const wailsConfig = readWailsConfig();
|
||||
const dryRun = rawArgs.includes('--dry-run');
|
||||
const refreshBindings = rawArgs.includes('--refresh-bindings') || process.env.GONAVI_REFRESH_WAILS_BINDINGS === '1';
|
||||
const skipInstall = rawArgs.includes('--no-install') || process.env.GONAVI_FAST_DEV_SKIP_INSTALL === '1';
|
||||
const passThroughArgs = rawArgs.filter((arg) => arg !== '--refresh-bindings' && arg !== '--no-install' && arg !== '--dry-run');
|
||||
const devServerUrl = process.env.GONAVI_FRONTEND_DEV_SERVER_URL || wailsConfig['frontend:dev:serverUrl'] || 'http://localhost:5173';
|
||||
const wailsjsRoot = path.resolve(projectRoot, wailsConfig.wailsjsdir || './frontend', 'wailsjs');
|
||||
const skipBindings = !refreshBindings && existsSync(wailsjsRoot);
|
||||
const fastArgs = ['dev'];
|
||||
|
||||
if (!skipInstall && !dryRun) {
|
||||
runFrontendInstall();
|
||||
}
|
||||
|
||||
if (!hasFlag(passThroughArgs, ['-m'])) {
|
||||
fastArgs.push('-m');
|
||||
}
|
||||
if (!hasFlag(passThroughArgs, ['-s'])) {
|
||||
fastArgs.push('-s');
|
||||
}
|
||||
if (!hasFlag(passThroughArgs, ['-nosyncgomod'])) {
|
||||
fastArgs.push('-nosyncgomod');
|
||||
}
|
||||
if (!hasFlag(passThroughArgs, ['-skipembedcreate'])) {
|
||||
fastArgs.push('-skipembedcreate');
|
||||
}
|
||||
if (skipBindings && !hasFlag(passThroughArgs, ['-skipbindings'])) {
|
||||
fastArgs.push('-skipbindings');
|
||||
}
|
||||
if (!hasFlag(passThroughArgs, ['-frontenddevserverurl'])) {
|
||||
fastArgs.push('-frontenddevserverurl', devServerUrl);
|
||||
}
|
||||
|
||||
if (!skipBindings && !refreshBindings) {
|
||||
console.warn('frontend/wailsjs not found; generating Wails bindings this run.');
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
const quoteArg = (arg) => (/\s/.test(arg) ? JSON.stringify(arg) : arg);
|
||||
console.log(`Would run: ${[wailsCommand, ...fastArgs, ...passThroughArgs].map(quoteArg).join(' ')}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const child = spawn(wailsCommand, [...fastArgs, ...passThroughArgs], {
|
||||
cwd: projectRoot,
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
console.error(`Failed to start Wails CLI: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "GoNavi",
|
||||
"outputfilename": "GoNavi",
|
||||
"frontend:install": "npm ci --prefer-offline --no-audit --fund=false --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000",
|
||||
"frontend:install": "node scripts/wails-frontend-install.mjs",
|
||||
"frontend:build": "npm run build",
|
||||
"frontend:dev:watcher": "npm run dev",
|
||||
"frontend:dev:serverUrl": "http://localhost:5173",
|
||||
|
||||
Reference in New Issue
Block a user