《Cocos 游戏开发实战》14.1 H5 构建与部署

第 13 章把性能调好之后,该把它构建成能发布的产物了。本节把 H5 构建面板上的每一项都给出实际取值——平台 Web Mobile、主包压缩类型 Merge Deps、MD5 Cache 勾选、Source Maps 关闭,讲清 build/web-mobile 的目录结构,给出静态托管的缓存与压缩策略,并附上可编译的平台判定与首屏预加载脚本。

14.1 H5 构建与部署

第 13 章结束时,《方块冲刺》在编辑器里已经跑得又快又稳。但「编辑器里能跑」和「用户能打开」之间隔着一整套构建与部署流程。这一章就干这件事:把同一个工程构建成 H5、微信小游戏、原生 Android / iOS 三端产物,并讲清每个平台构建面板上的参数到底该填什么。

H5 是最容易上手的一端,也是另外两端的参照——微信小游戏本质上是 H5 的裁剪形态,原生端则是把 JS 引擎与渲染后端换成原生的。先把 H5 的构建链路走通,后面两端就是换几个参数。

14.1.1 构建面板逐项取值

在编辑器里打开构建面板的步骤:菜单「项目 → 构建发布」→ 左上角「新建构建任务」→ 在「平台」下拉里选 Web Mobile → 填写参数 → 点「构建」。

每一项的实际取值如下(这是我们为《方块冲刺》定的正式发布配置):

选项取值理由
平台Web Mobile移动端 H5;桌面端另建一个 Web Desktop 任务
发布路径build/产物落在 build/web-mobile/
初始场景scenes/boot启动场景只放加载逻辑,主内容在 main
主包压缩类型Merge Deps合并依赖,产物脚本更少更小
MD5 Cache勾选文件名带内容哈希,可长期缓存
Source Maps不勾选正式包关掉,减小体积(13.3 讲过)
调试模式 Debug不勾选关闭调试信息
远程服务器地址留空首版全部资源随包发布,走 CDN 在 15.2 讲

「主包压缩类型」有三个取值,选错了会直接影响首屏:

取值行为适用
None每个模块单独成文件调试期,方便定位
Merge Deps按依赖关系合并成若干 chunk发布推荐
Merge All全部合并成一个大文件只在内网/极简场景用

Merge Deps 在体积与加载并发之间取得平衡;Merge All 会让首屏只有一个巨大的 JS 文件,浏览器无法并行下载、也无法增量解析,首屏反而更慢。MD5 Cache 则解决浏览器缓存问题:加了哈希的文件名在内容不变时永不失效,可以放心设置超长缓存期。

一个实际的操作顺序建议:先用 None 构建一版,出问题时脚本没被合并、报错行号最好定位;发布前切到 Merge Deps。两种配置的产物结构不一样,切换后要重新验证一次,别以为改一个下拉就万事大吉。

构建耗时也要有个预期:首次构建(引擎脚本要编译、资源要序列化)在小工程上通常一两分钟,大工程几分钟;后续增量构建会快一些,因为编辑器有构建缓存。CI 里每次都是干净环境,所以要按首次构建的时间估算流水线,别把超时设太短。

14.1.2 构建产物结构

构建完成后,build/web-mobile/ 下的结构大致如下:

build/web-mobile/
├── index.html            # 入口页,含 canvas 与启动脚本
├── application.js        # 引擎启动器,负责加载 settings 与主包
├── style.css             # 全屏与居中样式
├── src/
│   ├── settings.json     # 构建配置(场景列表、物理、渲染等)
│   ├── polyfills.js
│   ├── chunks/           # 业务脚本,按 bundle 分块
│   └── cocos-js/
│       └── cc.js         # 引擎运行时
└── assets/               # 序列化资源
    ├── main/             # 主包资源(含主场景)
    ├── internal/         # 引擎内置资源(默认字体等)
    └── bundle-levels/    # 关卡分包(15.1 讲)

注意三个关键点。第一,index.html 很薄,它只负责创建 canvas、加载 application.js,真正的启动逻辑在 src/ 里——所以别指望在 index.html 里塞初始化代码,改它会被下次构建覆盖。第二,src/settings.json 是运行时的「配置中心」,场景列表、设计分辨率、物理开关都在里面。第三,assets/ 下按 Bundle 分目录,bundle-levels 已经独立成目录,说明 15.1 的分包策略在构建阶段就已经生效。

产物能不能跑,先本地起个静态服务器验证,别急着上传。Cocos 的产物必须通过 HTTP 加载(直接双击 index.html 用 file:// 打开会因为跨域与 MIME 问题白屏):

cd build/web-mobile
python3 -m http.server 8000
# 浏览器打开 http://localhost:8000

这一步能挡住大多数「部署后才发现的低级错误」:路径写错导致的 404、MIME 类型不对导致脚本不执行、大小写不一致导致在 Linux 服务器上找不到文件。macOS 的文件系统不区分大小写,Linux 服务器区分——本地跑得好好的、上线就白屏,十有八九是这个原因,这也是全站内链校验脚本要专门检查大小写的原因。

14.1.3 部署:静态托管与缓存策略

H5 产物是纯静态文件,任何静态托管都能跑:Nginx、对象存储 + CDN、GitHub Pages 都行。但「能跑」和「跑得好」差在两点:压缩与缓存。

压缩:cc.js 与 chunks/*.js 都是文本,开启 gzip 通常能压到 25% 左右,brotli 更小。以我们这次构建为例,cc.js 未压缩约 1.6 MB,gzip 后约 420 KB,brotli 后约 350 KB——参考值,随引擎版本与业务代码变化,需在你的产物上实测。Nginx 里打开 gzip on; 并给 .js、.json、.wasm 加上 gzip_types 即可。

缓存:因为开了 MD5 Cache,文件名带内容哈希,所以可以给带哈希的文件设超长缓存:

# 带哈希的脚本与资源:一年
location ~* \.(js|json|wasm|png|jpg|webp|mp3)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# index.html 与 application.js:不缓存,保证能拿到新版本
location ~* (index\.html|application\.js)$ {
    expires -1;
    add_header Cache-Control "no-cache";
}

规则的核心是:入口文件不缓存,带哈希的资源永久缓存。这样用户每次打开都会拉最新的 index.html,而它引用的脚本若没变就直接命中本地缓存。反过来,如果把 index.html 也设成长缓存,用户就会一直看到旧版本,直到缓存过期——这是 H5 上线最常见的事故。

还有一条硬性要求:必须走 HTTPS。微信内置浏览器、部分剪贴板与分享 API 都只在 HTTPS 下可用,而且 HTTP 页面会被现代浏览器标记为「不安全」。本地调试用 http://localhost 即可,但线上域名一定要配证书。

如果不想自己维护服务器,对象存储 + CDN 是更省心的选择:把 build/web-mobile/ 整个目录上传到 OSS / COS / S3,开启静态网站托管,再绑 CDN 域名并打开 CDN 侧的 gzip 与缓存规则。唯一要注意的是入口文件与带哈希资源要用不同的缓存规则,CDN 控制台里通常支持按后缀或路径配置。上传时保持目录结构不变,别把 src/ 和 assets/ 的层级压平了。

14.1.4 L2:平台判定与首屏预加载

H5 构建跑起来后,第一件事是判断自己跑在哪个平台上——这决定了后面用哪套存档、哪套 SDK(12 章、15 章都会用到)。判断平台要用 sys.platform,不要用 UA 字符串去猜。下面这段组件同时演示平台判定与首屏场景的预加载进度。它只依赖 cc 的公开 API,能被类型检查。

import { _decorator, Component, director, sys } from 'cc';
const { ccclass } = _decorator;

@ccclass('BootFlow')
export class BootFlow extends Component {
  onLoad(): void {
    if (sys.platform === sys.Platform.WECHAT_GAME) {
      console.log('[boot] wechat minigame');
    } else if (sys.platform === sys.Platform.MOBILE_BROWSER) {
      console.log('[boot] mobile h5');
    } else if (sys.platform === sys.Platform.DESKTOP_BROWSER) {
      console.log('[boot] desktop h5');
    } else {
      console.log('[boot] other platform', sys.platform);
    }
  }

  startMainScene(onProgress: (p: number) => void): void {
    director.preloadScene(
      'main',
      (completed: number, total: number) => {
        onProgress(total > 0 ? completed / total : 0);
      },
      (err) => {
        if (err) {
          console.error('[boot] preload main failed', err);
          return;
        }
        director.loadScene('main');
      }
    );
  }
}

sys.Platform 是枚举,成员包括 MOBILE_BROWSER、DESKTOP_BROWSER、WECHAT_GAME、ANDROID、IOS 等,全部来自 cc.d.ts,写错编译不过。director.preloadScene 的第二个参数是进度回调,签名是 (completedCount, totalCount, item) => void;这里只用了前两个,第三个参数是当前加载项,用于显示「正在加载 xxx」。进度回调里要防 total 为 0 的除零,否则进度条会显示 NaN。

H5 端还有两件和平台强相关的初始化工作:设计分辨率适配与全屏请求。《方块冲刺》按 §C 的约定用 960 × 540、SHOW_ALL,两者都通过 view 设置:

import { screen, view, ResolutionPolicy } from 'cc';

// 与工程设定一致:960 × 540,SHOW_ALL 适配
view.setDesignResolutionSize(960, 540, ResolutionPolicy.SHOW_ALL);

// H5 端全屏必须由用户手势触发,浏览器不允许自动全屏
if (screen.supportsFullScreen) {
  screen.requestFullScreen().catch((e) => {
    console.warn('[screen] fullscreen failed', e);
  });
}

screen.requestFullScreen() 返回 Promise<void>,失败时要在 catch 里兜住——移动浏览器会拒绝非用户手势触发的全屏请求,这不是 bug,而是浏览器的策略。所以全屏请求应该绑在「开始游戏」按钮的点击回调里,而不是 onLoad。

14.1.5 L3:构建配置片段

构建面板上的参数最终写进工程的设置文件。下面是 settings/v2/packages/builder.json 里 H5 任务相关片段的节选。本段是 Cocos Creator 工程文件,需 Cocos Creator 3.8 编辑器打开,本节不可命令行复现。

{
  "__version__": "1.0.0",
  "options": {
    "platform": "web-mobile",
    "outputName": "web-mobile",
    "mainBundleCompressionType": "merge_deps",
    "md5Cache": true,
    "sourceMaps": false,
    "debug": false,
    "startScene": "scenes/boot",
    "server": ""
  }
}

在编辑器里打开构建面板,你会看到什么:平台下拉显示 Web Mobile,发布路径显示 build,初始场景是一个带下拉的资源选择框,显示 scenes/boot;右侧「高级选项」里,MD5 Cache 打勾、Source Maps 未打勾、Debug 未打勾;「主包压缩类型」下拉显示 Merge Deps;「远程服务器地址」输入框为空。改了这些值保存后,会写回上面那份 builder.json。

一个工程可以同时存在多个构建任务(H5、微信、原生各一个),它们共用同一份 builder.json 里的 options 结构、但按平台分开存。切任务时面板会自动加载对应平台的值,所以别在同一个任务里改来改去——新建任务比改参数更不容易出错。

builder.json 是要提交进版本库的,这样团队里每个人的构建参数才一致;而 build/ 目录要加进 .gitignore,产物不进版本库。这条约定 1.3 已经定过,本章的构建配置正好是它的一个具体落点。

另外,构建面板里可以保存多个任务并给它们起名(如 release-web、debug-web),切换任务比反复改参数更不容易出错,也更适合交给 CI 复用。

(以上工程文件需 Cocos Creator 3.8 编辑器打开,本节不可命令行复现。)

14.1.6 常见构建与部署问题

按经验,H5 上线前后最容易撞上的问题是这几类,提前记下来能省很多时间:

现象原因处置
本地白屏、控制台无报错用 file:// 直接打开了 index.html必须走 HTTP 服务器
线上白屏、404大小写不一致(macOS → Linux)全小写命名,或加产物链接校验
首屏卡在加载页场景资源太大,或 CDN 慢检查 main 场景资源量,15.3 讲启动优化
更新后仍是旧版本index.html 被长缓存入口文件设 no-cache
微信内置浏览器打不开未走 HTTPS配证书,线上强制 HTTPS
脚本报错行号全是打包后位置发布包关了 Source Maps临时构建调试包排查

其中「更新后仍是旧版本」和「微信内置浏览器打不开」是最典型的两类:前者是缓存策略配错,后者是协议配错。两者都不在引擎侧,而在部署侧,所以构建脚本之外还得有一份部署检查清单。

还有一个隐蔽的坑是多版本共存:如果 CDN 上同时存在多个带哈希的旧版本文件,而 index.html 又指向了新版,那用户拉到新版后旧文件其实已经没人用了。定期清理 CDN 上的历史版本能省存储,但要注意别删掉正在被灰度用户使用的版本——灰度的做法在 17.1 会讲。

14.1.7 收口

做完 H5 构建,工程里多了三样东西:

  • build/web-mobile/ 一份完整可部署的静态产物;
  • settings/v2/packages/builder.json 里的 web-mobile 构建任务配置;
  • 一份部署清单:index.html 不缓存、带哈希资源长缓存、开启 gzip / brotli、走 HTTPS。

上线前逐条走一遍这份检查表:

  • 本地 python3 -m http.server 打开无 404、无白屏;
  • 手机浏览器(含微信内置)能正常进入 main 场景;
  • 入口文件响应头是 no-cache,带哈希资源是长缓存;
  • gzip / brotli 已生效(curl -H "Accept-Encoding: gzip" -I 看 Content-Encoding);
  • 域名是 HTTPS,证书有效;
  • 发布包未开 Source Maps,体积已记录进基线文档。

验收标准是:把 build/web-mobile/ 丢到任意静态服务器上,用手机浏览器打开,能正常加载并进入 main 场景,控制台无报错。下一节把同一份工程构建成微信小游戏——那一步会多出几项 H5 没有的限制:主包体积上限、远程服务器地址必填、以及必须在微信开发者工具里预览。

阅读导航:上一节:目录 · 下一节:14.2 微信小游戏构建 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「cocos」更多文章

  1. 《Cocos 游戏联机实战》9.3 延迟补偿与体验权衡
  2. 《Cocos 游戏联机实战》9.2 网络状态 UI 与提示
  3. 《Cocos 游戏联机实战》9.1 同步表现与插值动画