Skip to content

构建与部署 — 练习

练习 1:分析 Makefile 构建目标链

阅读 Makefile 中的 WASM 相关目标,回答以下问题:

  1. make wasm 的完整依赖链是什么?从敲入命令到生成 pokeemerald.wasm,中间经历了哪些步骤?
  2. $(WASM_C_OBJS) 的编译规则中,为什么需要先经过 $(PREPROC) 预处理器?预处理器的输出为什么通过管道传给 Clang?
  3. wasm-ld--export-all 标志会将所有全局符号导出。这对最终 WASM 文件大小有什么影响?为什么项目仍然使用它?
参考答案
  1. 完整依赖链:

    make wasm
    └─ generated            (生成预处理文件和资源索引)
    └─ wasm-assets          (运行 generate_wasm_assets.py)
    └─ $(WASM_C_OBJS)       (编译所有 .c 文件为 .wasm.o)
    └─ $(WASM_DATA_OBJS)    (转换汇编数据文件为 .wasm.o)
    └─ $(WASM)              (wasm-ld 链接所有 .o → pokeemerald.wasm)

    具体步骤:预处理(生成头文件、资源索引)→ 资源扫描(Python 脚本)→ C 编译(Clang)→ 汇编数据转换(Python 脚本)→ 链接(wasm-ld)

  2. 预处理器的作用:

    • $(PREPROC) 是 pret/pokeemerald 的自定义预处理器,处理 INCBIN/INCGFX 等 GBA 特有宏
    • 它将 C 源码中的资源引用(如 INCBIN("graphics/pokemon/bulbasaur/anim_front.4bpp"))展开为 WASM 可识别的数据声明
    • 通过管道传递(-E $< | $(PREPROC) | $(WASM_CC) -x c)是因为预处理器的输出需要直接喂给 Clang 编译,不需要中间文件
  3. --export-all 的影响:

    • 导出所有全局符号会增加 WASM 文件中的导出表大小,但由于符号名只是元数据,对文件大小的实际影响较小(通常增加几十 KB)
    • 项目使用它的原因是为了支持 自动化测试和调试app.js 中的 automationApi() 需要访问 gSaveBlock1PtrgPlayerAvatargObjectEvents 等全局变量,以及 VarGet() 等函数
    • wasm_replay.mjs 也依赖导出的符号进行自动回放测试

练习 2:修改部署配置

假设你需要将 pokeemerald-wasm 部署到一个新的域名 emerald.example.com,并且希望添加一个 /health 端点返回服务状态。

  1. 需要修改 wrangler.toml 的哪些配置?
  2. 如果 WASM 文件超过了 Cloudflare Workers 的免费套餐大小限制(25MB),你会如何优化?
  3. 为什么 build_wrangler_site.mjs 在每次打包前要 rm -rf dist/cloudflare?如果去掉这行会有什么问题?
参考答案
  1. 修改 wrangler.toml

    toml
    [[routes]]
    pattern = "emerald.example.com"
    custom_domain = true

    需要将 pattern 改为新域名,并确保域名 DNS 指向 Cloudflare。添加 /health 端点需要使用 Workers 的路由功能,但由于当前项目仅使用静态资源托管([assets]),不执行 Worker 代码,需要额外添加一个 Worker 脚本来处理 API 路由。

  2. WASM 文件大小优化策略:

    • 使用 wasm-opt(Binaryen 工具包)进行后优化:wasm-opt -Oz pokeemerald.wasm -o pokeemerald.opt.wasm
    • 使用 wasm-snip 移除未使用的函数
    • 在链接时使用 -strip-all-strip-debug 去除调试信息
    • 启用 WASM 压缩(如 Brotli/Gzip),Cloudflare 默认支持自动压缩
    • 考虑使用 wasm-split 进行延迟加载
  3. rm -rf dist/cloudflare 的作用:

    • 确保每次打包都是干净的,不会残留上次的旧文件
    • 如果去掉这行,当文件名变更时(如删除了某个资源),旧文件会留在 dist 目录中,可能被意外部署
    • 对于 Cloudflare Workers 的静态资源部署,多余的文件会浪费部署包的空间

练习 3:设计 CI/CD 流水线

假设你要为 pokeemerald-wasm 设计一个 GitHub Actions CI/CD 流水线,请回答以下问题:

  1. 构建阶段需要安装哪些工具?列出完整的依赖列表
  2. 如何缓存构建产物以加速 CI?哪些目录适合缓存?
  3. 部署到 Cloudflare Workers 的 GitHub Actions 步骤应该怎么写?
参考答案
  1. 枇建依赖:

    • Clang(支持 wasm32 目标):apt install clang 或使用 LLVM 预编译包
    • wasm-ld:通常随 LLVM/Clang 安装,或来自 Rust 工具链(~/.rustup/toolchains/*/gcc-ld/wasm-ld
    • Node.js:运行 server.mjsbuild_wrangler_site.mjs
    • uv(Python 包管理器):运行 generate_wasm_assets.pywasm_asm_data.py
    • GNU Make:执行 Makefile
    • Wrangler CLInpm install -g wrangler,用于部署
  2. 缓存策略:

    • build/wasm/:缓存编译中间文件(.wasm.o),只有源文件变更时才重新编译
    • node_modules/:如果添加 npm 依赖
    • ~/.cache/uv/:Python 依赖缓存
    • Clang 编译缓存:如果启用了 ccache
    • 缓存 key 应包含 Makefile 的 hash 和源文件的 hash,确保依赖变更时缓存失效
  3. 部署步骤示例:

    yaml
    - name: Deploy to Cloudflare Workers
      env:
        CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
      run: |
        npm install -g wrangler
        make wrangler-site
        wrangler deploy

    需要在 GitHub 仓库的 Secrets 中配置 CLOUDFLARE_API_TOKEN

拓展挑战

  • 在本地运行 make serve-wasm,使用浏览器开发者工具的 Network 面板分析 WASM 文件的加载时间和大小
  • 修改 build_wrangler_site.mjs,添加 WASM 文件的 Brotli 压缩版本,并修改 index.html 使其优先加载压缩版本
  • 研究 Cloudflare Workers 的 [assets] 配置选项,添加合理的缓存策略(如 WASM 文件缓存 1 天,HTML 文件不缓存)
  • 设计一个 make serve-wasm-hot 目标,在源文件变更时自动重新编译并热重载浏览器页面