附录 C:环境与离线复现速查
正文里每一章的代码都假定你的环境与本书一致:Node 26.5.0、TypeScript 7.0.2、vitest 5.0.3、vite 8.3.4,外加一份真实的 Cocos Creator 3.8.8 类型声明。环境不一致时,报错会伪装成「代码写错了」,白白耗掉调试预算。这一页把所有版本、配置与命令固定下来,并给出断网时还能跑什么的核对表。
本附录里的每个版本号与每条命令都在本机实测过。标了「不可用」的写法是真实失败,不是传闻。
C.1 实测版本表
正文提到版本号时,以此表为准。
| 组件 | 版本 | 作用 |
|---|---|---|
| Node.js | 26.5.0 | 直接运行 .ts(只做类型剥离) |
| npm | 11.17.0 | 随 Node 安装 |
| TypeScript | 7.0.2 | tsc --noEmit 类型检查 |
| vitest | 5.0.3 | L1 逻辑单测与假时钟 |
| Vite | 8.3.4 | 打包成单文件 bundle |
| Playwright | 1.59.1 | 无头浏览器截图回归 |
@cocos/creator-types | 3.8.8 | 仅类型声明,供 L2 检查 |
| Cocos Creator | 3.8.8 | 全书锁定,编辑器本机未装 |
关键前提:mini2d(卷一绝大部分章节)是纯 TypeScript + 浏览器原生 Canvas2D,不引入任何游戏库。cc 只出现在 L2 片段里,且只用于类型检查,不参与运行。所以即使你完全没装 Cocos Creator 编辑器,也能读完全书前 17 章并跑通全部 mini2d 代码。
C.2 Node 26.5.0 安装与校验
C.2.1 安装
macOS 上用 Homebrew:
brew install node@26
Windows 上从官网下载 26.5.0 的 LTS 安装包,或使用 fnm / nvm-windows。无论哪种方式,装完都先校验版本——版本不对,后面每一步都会以奇怪的方式失败。
C.2.2 校验
node --version # 期望:v26.5.0
npm --version # 期望:11.17.0
Node 26 的关键特性是原生运行 TypeScript:node file.ts 无需编译、无需 ts-node。代价是它只做类型剥离(type stripping),不做语法转换——这条约束在 C.7 详述,它是本书代码风格的直接来源。
C.3 工作目录与依赖清单
C.3.1 工作目录
全书示例统一放在 /tmp/cocosbook:
mkdir -p /tmp/cocosbook/src
cd /tmp/cocosbook
目录划分遵循 1.1 节的三层职责:src/core/(循环、时钟)、src/math/(向量、矩阵)、src/render/(唯一碰 Canvas2D 的地方)、src/input/(输入)、src/game.ts(组装)。
C.3.2 package.json
{
"name": "mini2d",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@cocos/creator-types": "^3.8.8",
"@playwright/test": "^1.59.1",
"typescript": "^7.0.2",
"vite": "^8.3.4",
"vitest": "^5.0.3"
}
}
"type": "module" 让整个工程走 ESM。五个依赖全部是 devDependencies——mini2d 运行时零依赖,这些工具只在开发与测试阶段出现。
C.3.3 源切换
国内网络下 npm 官方源常超时,换成镜像源:
npm config set registry https://registry.npmmirror.com
npm config get registry # 回显确认
用 pnpm 的话同理:
pnpm config set registry https://registry.npmmirror.com
装依赖:
npm install # 或 pnpm install
装完 node_modules/@cocos/creator-types/engine/cc.d.ts 应当存在,体积约 3.3 MB——这是真实的 3.8.8 引擎 API 声明,本书所有 L2 符号都以它为准。
C.4 tsconfig.json 全文
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"erasableSyntaxOnly": true,
"experimentalDecorators": true,
"types": ["@cocos/creator-types/engine"]
},
"include": ["src", "*.ts"]
}
三个字段值得单独说明:
types: ["@cocos/creator-types/engine"]:加载engine.d.ts,其内容是一行/// <reference path="./engine/cc.d.ts"/>,于是declare module "cc"生效,from 'cc'不再报「找不到模块」。注意包名必须带/engine后缀,写成@cocos/creator-types无效。skipLibCheck: true:cc.d.ts有 3.3 MB、大量交叉类型,逐行检查既慢又会带出无关噪声;我们只关心自己写的代码是否对得上 API 签名。experimentalDecorators: true:@ccclass是实验性装饰器语法,Cocos 组件脚本的前提。erasableSyntaxOnly: true:让tsc主动拦住不可擦除语法,把 Node 26 的运行期约束提前到检查期。
C.5 vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['src/**/*.test.ts'],
environment: 'node',
},
});
environment: 'node' 是刻意的:src/core/ 与 src/math/ 不碰 DOM,测试就该在纯 Node 里跑,快且无浏览器噪声。需要 DOM 的渲染测试单独走 Playwright(C.8)。
C.6 vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
base: './',
build: {
outDir: 'dist',
target: 'es2022',
assetsInlineLimit: 100_000_000,
cssCodeSplit: false,
codeSplitting: false,
},
});
assetsInlineLimit 调到极大并关掉代码分割,是为了把 JS、CSS、图片全部内联进一个 index.html——无头浏览器载入一个本地文件即可截图,不必起服务器。base: './' 保证用相对路径引用资源。
C.7 Node 26 可擦除语法约束
Node 26 只支持类型擦除:所有 TS 代码必须只用可擦除语法——类型注解被逐字删掉后,剩下的必须是合法 JS。加转换开关(如 --experimental-transform-types)会直接报 bad option。
| 语法 | 能否擦除 | 替代写法 |
|---|---|---|
类型注解 x: number | 可以 | 直接用 |
interface / type 别名 | 可以 | 直接用 |
as 断言、泛型参数 | 可以 | 直接用 |
enum | 不行 | const 对象 + as const + 联合类型 |
namespace | 不行 | 普通模块导出 |
| 构造函数参数属性 | 不行 | 声明字段 + 函数体内赋值 |
enum 的替代写法(本书统一采用):
const GameState = {
Loading: 'loading',
Playing: 'playing',
Paused: 'paused',
} as const;
type GameState = (typeof GameState)[keyof typeof GameState];
// 用法:GameState.Playing,类型仍是字面量联合,编辑器可自动补全
const s: GameState = GameState.Playing;
构造函数里不要用参数属性。下面是不可用的写法(Node 26 会抛 SyntaxError):
// 不可用:参数属性会生成运行时代码,无法被擦除
class Runner {
constructor(public speed: number) {}
}
改成声明字段 + 函数体内赋值即可:
class Runner {
speed: number;
constructor(speed: number) {
this.speed = speed;
}
}
C.8 Playwright 安装与截图脚本
Playwright 用于 L1 的渲染端到端测试:把 mini2d 画出的画面截成 PNG 做回归。
npm install -D @playwright/test
npx playwright install chromium # 下载浏览器内核,只需一次
一个最小截图脚本 shot.mjs(用内置 Node 直接跑):
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 960, height: 540 } });
await page.goto('http://127.0.0.1:4173/', { waitUntil: 'networkidle' });
await page.waitForTimeout(300);
await page.screenshot({ path: 'shot.png' });
await browser.close();
npm run build
npm run preview & # 起本地 HTTP 服务,默认 4173
node shot.mjs
必须走 HTTP:ES module 脚本在 file:// 下会被 CORS 拦下,页面白屏(详见附录 D)。waitUntil 用 networkidle 而非 load——load 只等资源加载,不等渲染,截图容易抢在首帧之前。
C.9 零网络复现步骤
npm 的包缓存(~/.npm/_cacache)已包含上述依赖时,断网也能复现:
npm install --offline # 只从本地缓存安装,不联网
npm run typecheck
npm run test
npm run build
把这一节当成「断网时我还能跑什么」的核对表:
| 依赖 | 必需性 | 缺了会怎样 |
|---|---|---|
| Node 26.5.0 | 必需 | .ts 无法直接运行,可擦除语法约束也无从谈起 |
| TypeScript 7.0.2 | 必需 | 无法做 L2 类型检查 |
@cocos/creator-types@3.8.8 | 必需 | from 'cc' 报「找不到模块」 |
| vitest 5.0.3 | 推荐 | 逻辑单测跑不了,但示例脚本仍可 node 直跑 |
| vite 8.3.4 | 推荐 | 无法打包成单文件 bundle |
| Playwright + chromium | 可选 | 只影响第 16 章的截图回归 |
| Cocos Creator 3.8 编辑器 | 可选 | 只影响 L3 工程文件的查看,不影响本书任何可运行代码 |
断网也能跑完前 17 章:mini2d 零运行时依赖,测试零网络,截图只需本地 HTTP。只有 L3 的编辑器查看需要 GUI,而它本就不在命令行复现范围内。
C.10 一分钟自检
node --version # v26.5.0
npx tsc --version # Version 7.0.2
npm run typecheck # 无输出即通过(cc 类型已接通)
npm run test # 冒烟测试 1 passed
ls node_modules/@cocos/creator-types/engine/cc.d.ts # 存在,约 3.3 MB
五条都过,说明你的环境与本书一致。任何一条失败,先回到 C.2 的 Node 校验、C.3 的依赖清单、C.4 的 types 数组,或 C.7 的可擦除语法排查。环境问题不该占用你的调试预算——真正要花时间的是引擎机制本身。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。