Skip to main content
文档站副本

本页为语义契约的发布副本;请在上游 ZTSTest/Docs/spec 修改后执行 npm run sync-spec。(源:14-TYPESCRIPT.md

14 — TypeScript 工作流

约定 ZTS 工程如何用 TypeScript 编写业务脚本、生成 csharp: 声明、检查与发布到 QuickJS。 不改变 JavaScript 可见互操作语义;运行时仍只加载 ES module 源码(01-HOST-API.md §1.3)。 csharp: specifier / 导出名 → 02-TYPE-SYSTEM.md §2.11;zts.*05-LIB.md;调试 → build/04-JS-DEBUGGER.md

冲突裁决: 本文与 02 / marshal / metatable 冲突时,以那些文档的 JS 语义 为准。本文只约束编辑期 DX、工程布局与发布管线。


1. 目标与非目标

1.1 目标

约定
用 TS 写业务补全、跳转、tsc --noEmit、CI
对齐 csharp:import { T } from "csharp:…" 在 IDE 中为真模块(declare module
对齐 GetFunction发布后仍是 多模块 + named export;canonical specifier 不含 .js / .ts
声明与桥接同源生成的 .d.ts 类型集 = Il2Cpp Generate 会绑定的那批(不是另一份 typings 白名单)
声明入库TsProject/generated/** 纳入版本库;无 Unity 的 CI 也能 tsc --noEmit

1.2 非目标

态度
运行时执行 .ts不做;QuickJS 只跑 emit 后的 JS
默认 bundler(webpack / rollup 打成单文件)不做;会破坏 GetFunction(module, export)
DOM / JSX / 业务 npm 运行时依赖不在 v1 工作流
全量扫 UnityEngine 无 Generate 白名单不做
官方 CS.* 全局类型不做;迁移见 12-MIGRATION-ADAPTORS.md
import type 导入 C# 类型对象禁止(类型对象是运行时值,擦除后 new T 不存在)
tsc 链进 libil2cpp不做;编译在 Editor 本机 Node

1.3 锁定决策

#决策
1模块 specifiercanonical 不含 .js / .tsGetFunction("main", …)import { x } from "game/logic"
2生成声明入库TsProject/generated/
3进 Play 闸门tsc --noEmit 默认开启,Settings 可关
4检查 vs 发布检查用 tsc --noEmit;watch / 快速 emit 用 esbuild 1:1;二者 同一 outDir
5.d.ts 类型集Il2Cpp Generate 同源,禁止独立 typings 清单

2. 分层

IDE / CI tsc --noEmit + generated/*.d.ts + zts.d.ts
Emit tsc 或 esbuild 1:1 ESM(.ts → .js + .js.map)
Runtime moduleLoader / csharp: / CSharp / zts ← 既有语义
工具职责
类型包内 zts.d.ts + 生成的 declare module "csharp:…"仅编辑期
检查tsc --noEmitCI、提交前、进 Play 闸门
发布esbuild 不打包 的 1:1 transpile(或 tsc emit)产出 JS; minify export 名

禁止 用 esbuild 代替类型检查;禁止tsc 做 bundle。


3. 工程布局

.ts 不得 作为 Unity 可导入资源放在 Assets/(避免未知 importer)。源码在工程根:

<UnityProject>/
TsProject/
package.json # 仅 devDependencies:typescript、esbuild
tsconfig.json # extends 包内 tsconfig.base.json
src/ # 业务 TS(入库)
main.ts
game/logic.ts
generated/ # 生成声明(入库)
csharp/ # 每命名空间一个 .d.ts;文件名不含 ":"
out/ # emit;默认 gitignore
main.js
main.js.map
game/logic.js
Assets/StreamingAssets/ZTS/ # Player 构建拷贝;非 Editor 热路径权威源
Packages/com.code-philosophy.zts/ZTS~/types/
zts.d.ts
tsconfig.base.json

菜单 ZTS/Init TypeScript Project:把包内脚手架 复制TsProject/(UPM 只读,不在包内直接改工程 tsconfig)。

路径版本库
TsProject/src/**入库
TsProject/generated/**入库
TsProject/package.jsontsconfig.json入库
TsProject/out/**node_modules/gitignore
Assets/StreamingAssets/ZTS/**Player 构建产物;当作 Editor 开发权威源(可 gitignore 或由构建写入)

手写回归测试仍可放在 StreamingAssets/Tests/Js(纯 JS);强制迁 TS。


4. Canonical specifier(不含 .js

4.1 逻辑名

业务模块的 canonical specifier 是相对 TsProject/src/ 的 POSIX 路径,去掉 .ts / .js / .mjs

TS 源Canonical磁盘 emit
TsProject/src/main.tsmainTsProject/out/main.js
TsProject/src/game/logic.tsgame/logicTsProject/out/game/logic.js

GetFunction<T>("game/logic", "OnTick")jsModule 必须 是 canonical(不是 "game/logic.js")。

绝对 import { OnTick } from "game/logic"GetFunction 使用同一字符串。

4.2 相对导入与 .js 后缀

TypeScript ESM 在 .ts 源里写相对导入时 使用 .js 后缀(文件仍是 .ts):

import { helper } from "./util.js"; // 源文件:util.ts

emit 保留该字符串。QuickJS module_normalize 相对当前模块解析后,loader 可能收到带 .js 的名字。

规范: 进入宿主 moduleLoader / GetFunction 缓存键之前,将 specifier 规范化为 canonical

  1. 去掉前导 ./(仅当它是多余的;相对段仍先经 QuickJS normalize)
  2. 去掉尾缀 .js / .mjs / .ts
  3. 改写 csharp:02-TYPE-SYSTEM.md §2.11.1)

规范化后查 out/{canonical}.js。找不到 → loader 失败(明确异常),不得 再回退去读 .ts

4.3 与纯 JS 测试的兼容

现有 StreamingAssets/Tests/Js/*.js 允许磁盘文件带 .js。Loader 按 §4.2 把 assert.js 规范为 assert 再解析。Canonical 对外契约GetFunction、文档、TS from "…")一律不带后缀。


5. 包内手写类型

ZTS~/types/zts.d.ts 随 UPM 分发,覆盖:

  • 全局 ztsCSharpconsole(无 DOM)
  • zts.*05-LIB.md 一一对应
  • CSharp 为宽松索引签名(精确补全走 §6 生成模块)
  • ZTS.OpaqueHandleZTS.SzArrayZTS.GenericDef<N> 等 branded / 辅助类型

tsconfig.base.json 锁定:

选项值 / 要求
module / moduleResolutionESM(nodenext 或等价);相对导入带 .js
target / lib与当前 pin 的 QuickJS 可执行子集一致;禁止 "DOM"
stricttrue
noEmit仅用于「检查」tsconfig;emit 用另一 profile 或 esbuild
erasableSyntaxOnly开启(禁止 enum / namespace / 参数属性等非擦除语法)
verbatimModuleSyntax开启
types / include包内 zts.d.ts + TsProject/src + TsProject/generated

C# 类型是 :必须 import { Panel } from "csharp:…"禁止 import type { Panel }


6. csharp: 声明生成

6.1 文件与 declare module

Windows 文件名 不得:。生成路径用程序集 / 命名空间段,模块名 仍是运行时 specifier:

TsProject/generated/csharp/Assembly-CSharp/MyGame.UI.d.ts
declare module "csharp:Assembly-CSharp/MyGame.UI" {
export class Panel {
constructor();
setTitle(title: string): void;
}
}

不要 依赖 compilerOptions.paths 去映射 csharp: scheme。靠精确 declare module "csharp:…" 字符串匹配。

导出名、`$、无 arity 糖、嵌套模块、空命名空间模块:与 02-TYPE-SYSTEM.md §2.11 完全一致

6.2 类型集 = Il2Cpp Generate

生成器 读取与 Il2Cpp Generate 相同的类型/成员集合(EnsureBinding 会写入三表的 public 成员;含 [TsAlias] / [TsExtension] / MarshalAs 配置的最终 JS 面)。

允许禁止
Generate 会进 Player 绑定的类型与成员独立「typings」白名单
与 Generate 同步刷新(同一菜单或 Generate 的后置步骤)Editor 反射扫到、但 Generate 不会绑定的 API
开放泛型 定义List$1)若 Generate 包含该定义把未 Generate 的 UnityEngine 全量写入 .d.ts

Generate 未包含的类型 不得 出现在 csharp: named export 声明中(否则 Editor 能补全、Player 缺失)。

菜单:ZTS/Generate Typings;亦可挂在 ZTS/Generate/All 之后。C# / 桥接配置变更后须重新生成并 提交 generated/

6.3 成员到 TS 的映射

CLR.d.ts备注
classexport declare class Tnew T();静态在构造器上
structclass + static _default(): T
enum常量对象 + typenumber禁止 bigint
静态类private constructor()
开放泛型定义export const List: ZTS.GenericDef<1>(及 List$1不是 class List<T>new List() 须为类型错误
嵌套类型仅声明类型模块 生成 Outer.Inner 静态字段
无参属性属性只写则仅 setter
有参属性 / 索引器get_* / set_* 方法不要伪装 obj[i]
事件add_* / remove_*无 Event 子对象
重载多条函数签名运行时仍 dispatch(04-METHOD-OVERLOAD.md
ref / out / inZTS.OpaqueHandle<…>(C#→JS 默认)见 marshal byref
delegate 形参可调用函数类型可传 JS function
szarray 实例ZTS.SzArray<T>get / set / length不是 T[]T[]zts.to_array 返回值

闭合泛型 / 数组类型对象:继续 zts.make_*;其返回值在 P3 之前 可为 ZTS.TypeObjectnew 实例宽类型)。

6.4 分期(声明精度)

声明内容
P0手写 zts.d.ts + 宽松 CSharp;尚无生成 csharp: 也可写 TS(csharp: import 需 declare module 或临时 any
P1按 Generate 集生成 csharp::class / struct / enum + 成员(开放泛型为 GenericDef
P3make_generic_type 泛型推断、重载签名细化、可选 GetFunction 导出核对

P0 脚手架与 P1 生成器可分步落地;语义 以本节为准。


7. 工具链

7.1 检查

tsc --noEmit -p TsProject/tsconfig.json

Play 前执行(§8.1)。CI 无 Unity 时同样命令(依赖已入库的 generated/)。

7.2 Emit

场景工具输出
watch / Editor 快速刷新esbuild 1:1(format=esm bundle)TsProject/out/**/*.js + .js.map
无 esbuild 时tsc emit 到 同一 outDir同上

outDir 必须 与检查用 tsconfig 的 rootDir/outDir 布局一致(src/game/logic.tsout/game/logic.js)。

禁止 --bundle禁止 mangle named export(GetFunction 依赖导出名)。v1 默认不 minify

7.3 Node

Editor 本机需要 Node LTS(调用 npx tsc / npx esbuild)。包 内嵌 tsc 二进制。缺失 Node 时:菜单 / Play 闸门 失败并提示,禁止静默跳过检查(闸门关闭时除外,§8.1)。


8. Editor / Player

8.1 进 Play 闸门

Settings(概念字段,实现名可对齐 TsSettings):

默认行为
TypeScript Play 闸门进 Play 前:tsc --noEmit;失败 → 阻止 Play 并报告
闸门关闭可关仍建议 CI 跑检查

闸门开启时顺序:

  1. 若 Generate / C# 绑定脏 → 生成 typings(§6.2)
  2. tsc --noEmit
  3. out/ 相对 src/ 过期 → esbuild(或 tsc emit)

8.2 Editor 加载

moduleLoader(canonical)TsProject/out/{canonical}.jscsharp: 经 loader。

8.3 Player 构建

IPreprocessBuildWithReport(或等价):

  1. 可选:再跑 tsc --noEmit(失败中断出包)
  2. emit 到 out/
  3. 拷贝 out/**/*.js(及可选 .map)→ StreamingAssets/ZTS/
  4. Player moduleLoader 读 StreamingAssets, 依赖 Node、.ts

9. IDE 与调试

  • 用 VS Code / Cursor 打开 TsProject/,或 Unity 工程 + TsProject 的 multi-root。
  • includesrcgenerated、包内 zts.d.ts
  • emit 必须 带 source map。调试 hook 把 logical path 映射到 TsProject/src/**build/04-JS-DEBUGGER.md)。
  • csharp: 合成模块无源码;断点打在业务 .ts
  • erasableSyntaxOnly 下 map 接近 1:1。

10. GetFunction 与导出

TsAppDomain.GetFunction<Action<float>>("game/logic", "OnTick");
// TsProject/src/game/logic.ts
export function OnTick(dt: number): void { /* … */ }
  • jsModule = canonical(无 .js)。
  • named export;不把 export default 自动映射为 GetFunction 名(01-HOST-API.md §1.3)。
  • 不要csharp: 模块 GetFunction

P3 可增加「TS 导出 ↔ GetFunction 签名」核对; v1 硬性。


11. 与 adaptor 的边界

官方工作流12-MIGRATION-ADAPTORS.md
import { GameObject } from "csharp:UnityEngine.CoreModule/UnityEngine"CS.UnityEngine.GameObject
tsconfig 包含 CS 全局迁移工程可另生成 cs-global.d.tszts.d.ts 安装

新项目 安装 adaptor。


12. 验收

#标准
1GetFunction("main", …) / from "main" 均无 .js;相对 ./x.js 经 loader 规范为 canonical
2generated/csharp/** 入库;CI 无 Unity 可 tsc --noEmit
3Play 闸门默认开;tsc 失败阻止 Play;Settings 可关
4检查走 tsc;watch emit 走 esbuild 1:1;同一 outDir;无 bundle
5.d.ts 导出的类型 ⊆ Generate 绑定集;导出名与 §2.11 一致
6运行时不读 .ts / .d.ts;Player 只加载 StreamingAssets 中的 JS
7import type { CSharpClass } 在文档与 verbatimModuleSyntax 下不可作为类型对象值使用

13. 相关文档

文档内容
01-HOST-API.mdmoduleLoaderGetFunction、canonical specifier
02-TYPE-SYSTEM.md §2.11csharp: 运行时模块
05-LIB.mdzts.*zts.d.ts
11-MULTI-VERSION.mdZTS~/types 包内布局
12-MIGRATION-ADAPTORS.md非官方 CS.*
build/04-JS-DEBUGGER.mdsource map → TsProject/src