template/dev.md
2026-05-11 23:35:46 +08:00

106 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 魔塔样板开发说明
## 项目结构
- `public`: mota-js 样板所在目录
- `packages`: 核心引擎代码 monorepo
- `packages-user`: 用户代码 monorepo
- `src`: 游戏入口代码
依赖关系为单向:`src` → `packages-user``packages`。`packages` 与 `packages-user` 均可独立打包为库模式,`src` 为游戏的入口代码。
## 开发环境
- `node.js ^20.0.0 || >=22.0.0`
- `pnpm >= 10.0.0`
- 任意支持 `ESNext` 特性的浏览器
**建议使用 `vscode`,搭配 `prettier` `eslint` 插件。**
## 开发说明
1. 将项目拉取到本地。
2. 运行 `pnpm i` 安装所有依赖(如提示运行 `pnpm approve-builds`,请允许全部)。
3. 运行 `pnpm dev` 进入开发环境。
## 构建说明
| 命令 | 说明 |
| --------------------- | ------------------------------------------------------- |
| `pnpm build:packages` | 以库模式构建 `packages` 下的所有内容 |
| `pnpm build:game` | 构建为可直接部署的游戏包 |
| `pnpm build:lib` | 以库模式构建 `packages``packages-user` 下的所有内容 |
| `pnpm type` | 对仓库执行类型检查 |
| `pnpm check:circular` | 对仓库执行循环引用检查 |
## 开发原则
### 模块原则
- **无副作用**:所有模块只包含函数、类、常量的声明,不允许出现导出的变量声明或顶层代码执行,允许但不建议编写类的静态块。
- **模块初始化**:如需初始化,编写一个 `createXxx` 函数,在 `index.ts` 中整合后逐级向上传递,直至顶层模块统一执行。
- **不转发导出**:不允许一个文件导出不属于当前 monorepo 或当前文件夹的内容。
- **无循环引用**:不允许出现循环引用。若遇到不得不循环引用的情况,应首先反思接口设计是否存在问题。
### 命名规则
| 命名对象 | 规范 |
| ---------------------------------------------- | ------------------------------------------ |
| 变量、成员、一般常量、方法、函数 | 小驼峰 |
| 类、接口、类型别名、命名空间、泛型、枚举、组件 | 大驼峰 |
| 不变常量 | 全大写,单词间下划线分隔(如 `MAX_COUNT` |
| 专有名词缩写(如 `HTTP`、`URI` | 全大写 |
| 需被 `implements` 的接口 | 大写 `I` 开头 |
| HTML/CSS 中的 `id`、`class` 等 | 连字符命名法 |
不使用下划线命名法。
### 注释规范
- 公共方法、接口必须在**源头处**(多数情况下为 `interface`)添加 `jsDoc` 注释;其他常用成员、方法、类型也必须添加注释(含义极为明确或极少使用的可例外,但建议全部添加)。
- 继承或 `implements` 而来的 API方法、成员等若注释说明无需变更则**不应重复添加** `jsDoc` 注释。
- 不对构造器添加注释。若构造器使用了属性声明语法(`constructor(public prop: T)`)且成员需要说明,可仅对该成员添加参数注释,不写构造器描述。**在这种情况下**建议避免在构造器中使用属性声明语法,将成员单独声明并在构造器中赋值。此条建议**并非**要求不使用构造器的属性声明语法,而是仅在这一情况下不建议使用,常规情况下推荐使用此语法来缩短代码长度并提高可读性。
- 长文件可使用 `#region` / `#endregion` 分段以支持折叠。
- TODO 使用 `// TODO:``// todo:` 格式。
- 单行注释的 `//` 与注释内容之间留一个空格;不允许出现非 jsDoc 的多行注释,如需多行注释,使用多个单行注释代替。
- 注释合理换行:考虑中文字符较宽,建议每 4060 个字符在标点符号后换行,不允许在句中换行;参数注释换行后保持对齐。
- 单行注释结尾不加句号;较长的多行注释结尾可加句号。
- 一般不建议给接口(`inteface` 本身)、类型别名或类本身写注释(不好看),特殊情况除外。
### 类型规范
- 不允许出现非必要的 `any` 类型。
- 所有类的成员必须显式声明类型。
- 无法避免类型错误时,使用 `// @ts-expect-error` 标记并说明原因。
- 未使用的变量或方法以下划线开头命名。
- 合理使用 `readonly`、`protected`、`private` 关键字。
- 可选参数过多时,考虑改用对象参数。
- 尽量避免 `as` 类型断言,除非必要。
### 其他要求
- 不使用字符串作为键或特殊标识符(如枚举值、事件名、状态名等),应使用枚举代替。仅当明确表示字符串本身(如字符串类型的 id 别名、文件路径等)时方可使用字符串字面量。
- 严格遵循 `eslint` 配置,不允许出现 eslint 报错。
- 尽量不使用 `?.` 运算符,仅推荐在以下两种场景中使用:
- 副作用函数调用,如 `this.obj?.func()``this.obj.func?.()`
- 对象 Required 化,如 `{ value: obj?.value ?? 0 }`
- 只进行必要的非空判断,非必要时直接使用非空断言 `!`
- 除非参数要求传入函数等情况,不建议在函数内定义局部函数。
- 语句尽量不换行,除非必要,尤其注意三元运算符与 `private readonly` 类成员。
## 双端分离
样板将渲染端与数据端彻底分离:
- **数据端**:可在 `node` 环境中单独运行,可直接用于录像验证,不负责任何渲染逻辑。
- **渲染端**:仅负责向数据端发送消息,不负责任何逻辑运算。
| 包 | 层级 | 说明 |
| ---------------------- | ------------ | ------------------------------------ |
| `@user/data-base` | 数据端系统层 | 负责数据端核心系统 |
| `@user/data-state` | 数据端实现层 | 依赖系统层实现完整的游戏实例 |
| `@user/client-base` | 渲染端系统层 | 负责渲染端核心系统 |
| `@user/client-modules` | 渲染端实现层 | 依赖系统层实现客户端的渲染与用户交互 |
数据端允许调用渲染端代码,但必须使用全局接口 `Mota.r(() => {})` 包裹。除非必要,否则不建议在数据端调用渲染端代码。