# MeshRay - 标准构建流程指南

**最后更新**: 2026-03-24  
**关键认识**: 前端必须先 npm build，embed.go 会被 Vite 清空！

---

## 🎯 **核心要点**

### ⚠️ **重要认识**

1. **前端修改后** → 必须执行 `npm run build`
2. **Vite 编译时** → 会清空 `dist/` 目录
3. **embed.go 被删** → 需要重新创建
4. **最后编译后端** → `go build`

**错误做法** ❌:
```bash
# ❌ 一直编译后端，前端不重新构建
go build
go build
go build
```

**正确做法** ✅:
```bash
# ✅ 先构建前端，再构建后端
npm run build
# 重新创建 embed.go
go build
```

---

## 📋 **完整构建流程**

### 流程图：

```mermaid
graph TD
    A[开始] --> B{修改了什么？}
    B -->|前端代码 | C[npm run build]
    B -->|后端代码 | G
    C --> D[Vite 编译 dist/]
    D --> E[⚠️ embed.go 被删除]
    E --> F[重新创建 embed.go]
    F --> G[go clean -cache]
    G --> H[go build 编译后端]
    H --> I[重启服务]
    I --> J[测试访问]
    J --> K{成功？}
    K -->|是 | L[✅ 完成]
    K -->|否 | M[检查日志]
```

---

## 🔧 **详细步骤**

### Step 1: 前端构建 ⭐⭐⭐

```bash
cd web
npm run build
```

**输出示例**:
```
✓ built in 13.41s
```

**生成的文件**:
```
dist/
├── index.html
├── assets/
│   ├── index-AIAUiq89.js
│   ├── Login-Brnne-RL.js      ← 登录页组件
│   ├── Dashboard-DSsX0xA1.js  ← 仪表盘组件
│   └── ... (共 44 个文件)
```

**注意**: 
- ⚠️ Vite 会清空整个 `dist/` 目录
- ⚠️ `embed.go` 会被删除
- ✅ 每次都会生成新的 hash 文件名

---

### Step 2: 重新创建 embed.go ⭐⭐⭐

**方法 A: PowerShell 单行命令**（推荐）

```powershell
@"
package dist

import "embed"

//go:embed *
var WebAssets embed.FS
"@ | Out-File -FilePath "web\dist\embed.go" -Encoding utf8 -NoNewline
```

---

**方法 B: 使用 cat 命令**（Linux/Mac）

```bash
cat > web/dist/embed.go << 'EOF'
package dist

import "embed"

//go:embed *
var WebAssets embed.FS
EOF
```

---

**方法 C: 手动创建文件**

文件路径：`web/dist/embed.go`

内容：
```go
package dist

import "embed"

//go:embed *
var WebAssets embed.FS
```

**验证**:
```bash
Test-Path "web\dist\embed.go"
# 返回：True ✅
```

---

### Step 3: 清理 Go 缓存

```bash
go clean -cache
```

**作用**:
- 清理编译器构建缓存
- 确保重新编译所有包
- 避免奇怪的编译错误

---

### Step 4: 编译后端 ⭐⭐⭐

```bash
go build -o meshray.exe -ldflags "-s -w -H=windowsgui" ./cmd/meshray
```

**参数说明**:
- `-o meshray.exe`: 输出文件名
- `-s -w`: 去除符号表和调试信息（减小体积）
- `-H=windowsgui`: Windows GUI 模式（无控制台窗口）

**编译时间**: 约 5-10 秒

---

### Step 5: 重启服务

```bash
# 停止旧服务
Get-Process -Name "meshray*" -ErrorAction SilentlyContinue | Stop-Process -Force

# 启动新服务
.\meshray.exe
```

**验证启动**:
```bash
Get-Content "logs\meshray.log" -Tail 3
```

**期望输出**:
```json
{"message": "Starting MeshRay", "address": ":9531"}
```

---

### Step 6: 测试访问

```bash
curl.exe http://localhost:9531/
```

**期望返回**:
```html
<!DOCTYPE html>
<html lang="zh-CN">
  <title>MeshRay - 简单、高效异地组网</title>
  <script src="/assets/index-AIAUiq89.js"></script>
```

---

## 🛠️ **一键构建脚本**

创建文件：`build.ps1`

```powershell
#!/usr/bin/env pwsh

# MeshRay 标准构建脚本
Write-Host "🔨 开始构建 MeshRay..." -ForegroundColor Cyan

# 1. 前端构建
Write-Host "`n📦 步骤 1: 构建前端..." -ForegroundColor Yellow
Set-Location web
npm run build
if ($LASTEXITCODE -ne 0) {
    Write-Host "❌ 前端构建失败！" -ForegroundColor Red
    exit 1
}
Set-Location ..

# 2. 重新创建 embed.go
Write-Host "`n📝 步骤 2: 创建 embed.go..." -ForegroundColor Yellow
@"
package dist

import "embed"

//go:embed *
var WebAssets embed.FS
"@ | Out-File -FilePath "web\dist\embed.go" -Encoding utf8 -NoNewline

if (-not (Test-Path "web\dist\embed.go")) {
    Write-Host "❌ embed.go 创建失败！" -ForegroundColor Red
    exit 1
}

# 3. 清理 Go 缓存
Write-Host "`n🧹 步骤 3: 清理 Go 缓存..." -ForegroundColor Yellow
go clean -cache

# 4. 编译后端
Write-Host "`n🔨 步骤 4: 编译后端..." -ForegroundColor Yellow
go build -o meshray.exe -ldflags "-s -w -H=windowsgui" ./cmd/meshray
if ($LASTEXITCODE -ne 0) {
    Write-Host "❌ 后端编译失败！" -ForegroundColor Red
    exit 1
}

# 5. 重启服务
Write-Host "`n🔄 步骤 5: 重启服务..." -ForegroundColor Yellow
Get-Process -Name "meshray*" -ErrorAction SilentlyContinue | Stop-Process -Force
Start-Sleep -Seconds 2
Start-Process ".\meshray.exe"

# 6. 等待启动
Start-Sleep -Seconds 3

# 7. 测试访问
Write-Host "`n🧪 步骤 6: 测试访问..." -ForegroundColor Yellow
$response = curl.exe http://localhost:9531/ -s
if ($response -match "DOCTYPE") {
    Write-Host "`n✅ 构建成功！前端页面可访问" -ForegroundColor Green
} else {
    Write-Host "`n❌ 测试失败！请检查日志" -ForegroundColor Red
    Get-Content "logs\meshray.log" -Tail 10
    exit 1
}

Write-Host "`n✨ 完成！" -ForegroundColor Green
Write-Host "📱 访问地址：http://localhost:9531" -ForegroundColor Cyan
```

**使用方法**:
```bash
.\build.ps1
```

---

## 📊 **常见场景处理**

### 场景 1: 只修改了前端

```bash
cd web
npm run build
# 重新创建 embed.go
@"
package dist
import "embed"
//go:embed *
var WebAssets embed.FS
"@ | Out-File -FilePath "dist\embed.go" -Encoding utf8 -NoNewline
cd ..
go build
# 重启服务
```

---

### 场景 2: 只修改了后端

```bash
go clean -cache
go build
# 重启服务
```

---

### 场景 3: 前后端都修改了

```bash
# 完整流程
cd web; npm run build; cd ..
# 重新创建 embed.go
go clean -cache
go build
# 重启服务
```

---

### 场景 4: 快速迭代开发

**开发模式**（不嵌入静态资源）:

```bash
# 终端 1: 前端开发服务器
cd web
npm run dev

# 终端 2: 后端开发（监听变化）
air  # 或使用其他热重载工具
```

**生产构建**:
```bash
# 使用完整构建流程
.\build.ps1
```

---

## ⚠️ **常见错误与解决**

### 错误 1: embed.go 丢失

**症状**:
```
no required module provides package git.zkcoi.com/zkcoi/meshray/web/dist
```

**原因**: Vite 清空了 dist/ 目录

**解决**:
```bash
# 重新创建 embed.go
@"
package dist
import "embed"
//go:embed *
var WebAssets embed.FS
"@ | Out-File -FilePath "web\dist\embed.go" -Encoding utf8 -NoNewline
```

---

### 错误 2: 前端修改不生效

**症状**: 页面还是旧的

**原因**: 没有重新编译前端

**解决**:
```bash
cd web
npm run build
# 然后重新创建 embed.go 并编译后端
```

---

### 错误 3: MIME 类型错误

**症状**:
```
Expected JavaScript but got text/html
```

**原因**: 路由顺序错误或浏览器缓存

**解决**:
1. 确认 `server.go` 中 StaticFS 在 NoRoute 之前
2. 清除浏览器缓存（Ctrl+Shift+R）
3. Network 标签勾选 Disable cache

---

### 错误 4: 编译产物 hash 变化

**症状**: 
- 这次是 `Login-ABC123.js`
- 下次是 `Login-XYZ789.js`

**原因**: Vite 的内容哈希策略

**解决**: 正常现象，无需处理。每次 build 都会生成新 hash

---

## 📋 **检查清单**

构建前检查：

- [ ] ✅ 前端代码已保存
- [ ] ✅ 后端代码已保存
- [ ] ✅ Git 状态干净（可选）

构建中检查：

- [ ] ✅ `npm run build` 成功
- [ ] ✅ embed.go 已重新创建
- [ ] ✅ `go build` 无错误

构建后检查：

- [ ] ✅ 服务正常启动
- [ ] ✅ curl 测试返回 HTML
- [ ] ✅ 浏览器访问正常
- [ ] ✅ Console 无错误

---

## 💡 **最佳实践**

### 1. 开发习惯

**每次修改前端后**:
```bash
npm run build
# 立即测试，不要等
```

**每次修改后端后**:
```bash
go build
# 重启服务测试
```

---

### 2. 版本控制

**.gitignore 配置**:
```gitignore
# 构建产物
web/dist/*
!web/dist/.gitkeep
*.exe
*.log

# 但保留 embed.go 模板
!web/dist/embed.go.template
```

**建议**: 创建 `embed.go.template` 作为模板

---

### 3. 自动化

**VS Code Tasks**:

`.vscode/tasks.json`:
```json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Build Frontend",
      "type": "shell",
      "command": "npm run build",
      "options": {
        "cwd": "${workspaceFolder}/web"
      }
    },
    {
      "label": "Build Backend",
      "type": "shell",
      "command": "go build -o meshray.exe -ldflags \"-s -w -H=windowsgui\" ./cmd/meshray",
      "group": "build"
    }
  ]
}
```

---

### 4. CI/CD 集成

**GitHub Actions 示例**:

```yaml
name: Build

on: [push, pull_request]

jobs:
  build:
    runs-on: windows-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
    
    - name: Setup Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21'
    
    - name: Build Frontend
      run: npm run build
      working-directory: ./web
    
    - name: Create embed.go
      shell: pwsh
      run: |
        @"
        package dist
        import "embed"
        //go:embed *
        var WebAssets embed.FS
        "@ | Out-File -FilePath "web\dist\embed.go" -Encoding utf8 -NoNewline
    
    - name: Build Backend
      run: go build -o meshray.exe -ldflags "-s -w -H=windowsgui" ./cmd/meshray
    
    - name: Upload Artifact
      uses: actions/upload-artifact@v3
      with:
        name: meshray
        path: meshray.exe
```

---

## 🎯 **总结**

### 核心口诀：

> **前端改，npm build**  
> **Vite 清，embed 没**  
> **重新建，莫要忘**  
> **后端编，服务启**

---

### 关键记忆点：

1. ⭐ **前端修改必 npm build**
2. ⭐ **Vite 会清空 dist/**
3. ⭐ **embed.go 需要重建**
4. ⭐ **最后才能 go build**

---

### 一句话总结：

**修改前端 → npm run build → 重建 embed.go → go build → 重启服务**

---

**状态**: ✅ **正确的构建流程已明确**  
**下次记住**: 前端修改后先 npm build，别忘了重建 embed.go！

*MeshRay - 流程规范，事半功倍！* ✨🔧
