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 微信小游戏构建 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。