Appearance
gis-cesium 扩展 SDK 演进基线
状态
已采纳,作为 @nexa/gis-cesium 后续架构优化的判断基线。
当前基线(2026-08 实测)
| 门禁 | 命令 | 结果 |
|---|---|---|
| 类型检查 | pnpm --filter @nexa/gis-cesium run typecheck | ✅ 通过 |
| 类型检查 | pnpm --filter @nexa/scenario-data run typecheck | ✅ 通过 |
| 单元测试 | pnpm --filter @nexa/gis-cesium run test | ✅ 280 passed / 41 files |
| 库构建 | pnpm --filter @nexa/gis-cesium run build:lib | ✅ es.js 460KB(gzip 122KB)/ umd 371KB(gzip 110KB),Cesium 外部化 |
| ESLint(全仓) | pnpm exec eslint . --ignore-pattern "**/dist-app/**" | ✅ 0 error / 221 warning |
全仓 lint 的 221 个 warning 属可接受的存量噪音(空接口、缺返回类型、
no-unsafe-*等),在 P1~P3 逐批收敛;规则是禁止新增 error,不是一次性清零 warning。前置工程修正(P0 前置条件):
dist-app加入.gitignore(消除 ~190 万幽灵 lint 错误);eslint.config.js的parserOptions.project覆盖gis-cesium与common-data两个包的tsconfig.app.json,并忽略vite.config.ts/worker.ts(消除跨包解析错误)。
定位
gis-cesium 是 CesiumJS 扩展 SDK,不是替代 CesiumJS 的全量引擎,也不是禁止上层接触 Cesium 的防腐层。
SDK 的价值集中在四类问题:
- 跨产品可复用的 GIS 能力
- 多个 Cesium 对象协同形成的高阶能力
- 事件、DOM、GPU 和场景对象的资源生命周期
- 性能策略、版本差异与私有 API 风险隔离
架构决策
- 保留 Cesium 稳定公共类型的可组合性
CesiumViewer作为可选托管入口,原生Cesium.Viewer作为一等集成对象- Effect、Plugin、Manager 必须声明资源所有权并提供幂等清理
- 私有 API 逐步迁移到兼容适配层,业务模块不得新增私有 API 访问
- 公共入口必须保证开发源码入口、ES 构建和 UMD 构建行为一致
- 示例属于运行时契约测试的一部分,不能替代自动化测试
分阶段治理
P0:停止继续积累 ✅ 已完成
- ✅ 修复 typecheck、材质注册和 Effect 生命周期的确定性错误
- ✅ 全仓 lint error 清零(0 error / 284 warning):新代码禁止
any、@ts-nocheck和新增 Cesium 私有 API - ✅ 为新增能力建立导出、单测、示例、文档的完整证据链
- ✅ 全仓
@ts-nocheck移除(插件 mixin、帧缓冲工具等),剩余私有 API 用最小化类型 +as unknown as收敛
P0 验收命令
bash
pnpm --filter @nexa/gis-cesium run typecheck
pnpm --filter @nexa/scenario-data run typecheck
pnpm --filter @nexa/gis-cesium run test # 期望 245 passed
pnpm exec eslint . --ignore-pattern "**/dist-app/**" # 期望 0 errorP0 遗留(转入 P1~P3)
- 存量 284 个 lint warning 未清零(不阻塞发布)
- 材质模块的
Cesium.Property私有断言已统一迁移至propertyUtils助手;其余私有 API 访问待 P2 适配层收口
P1:统一扩展契约 ✅ 已完成
- ✅ 生命周期契约:新增
core/lifecycle.ts的Destroyable/Lifecycle<THost>类型并公开导出;ConnectionManager/LayerManager补destroy()幂等守卫,DrawManager.destroy()加二次调用守卫;19 个 effect 全部统一到on/off/destroy(viewer: Cesium.Viewer)单一签名族(含ExplosionEffect重构为延迟创建),ExcavateEffect删除assertExcavateViewer断言;lifecycle.spec.ts+ 共享mockViewer.ts提供契约级测试 - ✅ 统一 Viewer 注入:
CesiumViewer.extend(mixin)托管入口 +extendViewer(viewer, mixin)函数式助手共用同一实现,消除幽灵 APIviewer.extend(示例 0 残留);EntityManager/LayerManager构造改为位置参数(删除EntityManagerOptions/LayerManagerOptions);新增core/host.ts宿主全局策略,window.Cesium/Vue/echarts/ol全部经类型化访问器收口,消掉(window as any).echarts - ✅ 材质注册幂等 + 入口一致:
registerAll副作用移入NexaCesium.ts顶部,dev 源码 / lib index / ES-UMD 三路入口行为一致(G4);CsmMaterial守卫补_materialCache缓存检查,与registerAll的reg()跨路径幂等(G1);Material1()加缓存守卫 + 宿主全局存在性检查,并在viewerToolsMixin安装时前置调用(G2/G3);新增registerAll.spec.ts、扩展Material.spec.ts、viewerToolMixin.Material1.spec.ts
P1 实测基线(2026-08-07)
text
typecheck gis-cesium ✅ / common-data ✅
test 259 passed(P0 基线 245 + 新增 14)
build:lib ES/UMD 双产物注册行为一致
eslint 0 error / 284 warning(存量警告,未新增 error)
grep 0 处 viewer.extend( · 0 处 assertExcavateViewerP1 遗留(转入 P2)
- Mixin 返回对象形状统一:
activate/deactivate/clear/destroy词汇表统一(positionPick用add/removeAll、grid的show是数据属性等),跨 ~15 个 mixin 大面重构。P2 历史先例:当时是无迁移路径的直接重构;按 P3 治理规则(docs/guides/release-policy.md),未来此类公共形状变更须先走弃用周期(@deprecated+@see替代,保留 ≥1 版本) - 共享资源全局销毁协议:
TextureManager双单例、window.echarts._cesiumCoordRegistered标志 - registerAll 补
Material[XType]静态量(现代 Cesium 的fromType不依赖) - Cesium 私有 API 适配层:
_materialCache等私有访问的收口(随 P2 兼容适配层一并处理)
P2:隔离兼容风险
P2-A 已完成(私有 API 适配层收口,grep 59 → 38)。 P2-B1 已完成(Scene 内部类型 shim 精化 + Property 静态量迁移,grep 38 → 26)。 P2-B2 已完成(viewer 级销毁协议 + 资源单例统一 + Mixin 形状外科手术,grep 26 → 24)。
- ✅ 建立 Cesium 私有 API 兼容适配层(
core/cesium-compat/materialCache.ts,material-cache 族唯一私有访问点,统一${type}Type静态约定) - ✅ 类型 shim(
cesium.d.ts增强Viewer._clockViewModel/Timeline.currentTime·makeLabel,随 shim 同批删除 8 条@ts-expect-error) - ✅ 建立 Cesium 升级清单和回归矩阵(
docs/guides/cesium-upgrade-checklist.md:锁定版本、9 族私有 API 表面矩阵、版本敏感能力 → 覆盖映射、升级 SOP) - ✅ 对版本敏感能力增加浏览器/WebGL 集成测试(Playwright 无头 Chromium 冒烟 5 用例,
pnpm --filter @nexa/gis-cesium run test:e2e) - ✅ P2-B1:Scene 内部类型 shim 收口——
src/types/cesium-internal.d.ts从any精化为具型形状(DefaultView/FrameState/Context/UniformState等),补Framebuffer/Texture构造器 +createPropertyDescriptor+Cesium3DTilePass命名空间;viewerOffscreenRenderMixin5 处、createFrameBuffer2 处冗余强转清零,_hdr→ 公开scene.highDynamicRange等价替换(其余_defaultView/_frameState/...无公开替代,保留 shim 具型) - ✅ P2-B1:Property 静态量迁移(net-zero)——
viewerToolMixinMaterial1 的isConstant/equals/getValueOrClonedDefault迁至propertyUtils,createPropertyDescriptor入 shim;toProperty普通as助手(不命中 grep 门禁)
P2 验收命令
- 私有 API 访问点数量不反弹:对
grep -rn "as unknown as\|@ts-expect-error\|@ts-ignore" packages/gis-cesium/src/lib保留计数快照,P3 前逐批下降 - P2-A 快照:59(P1 基线)→ 38;P2-B1 快照:38 → 26;P2-B2 快照:26 → 24;
test:e2e6 用例通过(案例 c 守护 FBO 离屏渲染路径,案例 f 守护 viewer 销毁协议)
P2-B2 完成记录(2026-08-07)
- ✅ viewer 级销毁协议:
core/plugins/extend.ts新增 per-viewer uninstall 钩子注册表(registerViewerDestroyHook/runViewerDestroyHooks/destroyViewer,WeakMap 键控、钩子参数传 viewer、逐 hook 吞错);ViewerMixin返回类型扩为void | ((viewer) => void);extendViewer执行成功后才登记钩子 + 幂等去重;AirViewer.destroy()顺序 = 跑钩子 → 还原 Camera 静态量 →viewer.destroy() - ✅ 12 个 mixin 补 uninstall 钩子(保留公开命名):现成 destroy 复用(positionPick/measure/sightLine/echartLayer/entity/offscreenRender/overviewMap)+ 缺陷修复(grid 双 camera 监听句柄 +
show委托、cameraControl 补stopRoam()、tooltip style 引用计数、animationPanel onTick 清理 + 高度还原、scenarioConfig formatter 惰性快照还原);不接 hook 的 mixin 及理由:tilesClip(stateless,clip 只改调用方 tileset)、tool(Material1 全局幂等注册 + 逐实体创建)、simulationBus(Effect 自带 Lifecycle + setTimeout 自清)、material(模块级注册表)、nodeToTransform/relativePath/axisVector(纯函数 / 逐实体) - ✅ TextureManager 双单例统一:
core/utils/textureManager.ts单一实现,${type}@${size}复合键唯一化 + 逐字调色板表(fire/smoke @64/128 四组),dispose()复位单例;viewerSimulationBusMixin(64)与ExplosionEffect(128)行为不变;不接 viewer 销毁钩子(纹理跨 viewer 共享,viewer 非所有者) - ✅
_cesiumCoordRegistered按 viewer 键控:viewerEchartLayerMixin删全局布尔门禁,每次addLayer无条件registerCoordinateSystem('cesium', createCoordSystem(viewer))(echarts 按名覆盖、重复注册安全)——修复「首次注册绑定已销毁 viewer」错误形态;单 viewer 假设:多 viewer 并行渲染仍受 echarts 全局 last-wins 限制 - ✅ Camera.DEFAULT_VIEW 保存/还原守卫:
AirViewer构造前快照DEFAULT_VIEW_RECTANGLE/FACTOR,destroy 还原;同时修复AirViewercredit 处理用appendChild(textNode)替代innerHTML=覆写(innerHTML清空 CreditDisplay 内部子容器会使 Cesium 销毁时removeChild报 "not a child") - ✅ 散点收口:
EntityDetectEffect的null as unknown as Entity×2 →source/target: Entity | null(grep 26 → 24) - ✅ 测试:
extend.spec.ts(8)+textureManager.spec.ts(8)+plugins.specechart 按次重注册(+1)+ e2e 用例 f(销毁协议 + Camera 同一性还原 + 重建);test280 passed / 41 files,test:e2e6/6 - ✅ B2 遗留清零:共享资源销毁协议、Camera 守卫、Mixin 形状外科手术、散点强转全部闭环;
Model._runtimeNode普通as断言(0 grep)按 B1 决策维持现状
P3:性能与发布治理
- 建立 Entity/Primitive 数量级基准和内存基线
- 对包体积、公共导出和类型声明做自动化检查
- 使用弃用周期管理公共 API 演进,禁止无迁移路径的破坏性修改
P3 完成记录(2026-08-07)
- ✅ P3-A 数量级基准:新增
benchmarks/entity-primitive-scale.spec.ts(5 用例,test:bench)——1000 point entity 添加、弱 O(n²) 对照(1000 vs 100 耗时倍数)、EntityManager.createBatch500 条、removeAll归零、1000new Primitive();宽松绝对上限防灾难性回归,不精确计时 - ✅ P3-A e2e 泄漏基线:
tests/e2e/cesium-smoke.spec.ts用例 g——真实 SwiftShader WebGL 下 200 point entity + 1 primitive 创建→render→removeAll()+destroy()→isDestroyed()→重建新 viewer 断言entities.values.length === 0、scene.primitives.length === 0(跨 viewer 资源归零;内存字节不断言——SwiftShader 下performance.memory噪音大,资源计数是确定性代理) - ✅ P3-B 体积门禁:
scripts/check-bundle.mjs(check:bundle)——es raw ≤ 520KB / gzip ≤ 140KB、umd raw ≤ 420KB / gzip ≤ 125KB、css ≤ 20KB;PNG 底图资产(15.45MB,AirViewer 默认底图)仅报告不设限(设计决策,后续可考虑外置) - ✅ P3-B 治理门禁:
scripts/check-governance.mjs(check:governance)——导出面 manifest 与public-api.baseline.json(538 个公共导出)比对:删除导出 exit 1(禁止无迁移路径破坏)/ 新增提示 / kind 变化 warn;d.ts 完整性(入口非空、export 链可解析、package.json 声明的类型入口可解析);版本一致性断言(package.json.version ===NexaCesium.ts的VERSION字面量);@deprecated缺@see/替代说明 warn - ✅ P3-B CI:新建
.github/workflows/ci.yml——push+PR 触发,ubuntu + pnpm 10 + Node 24,全量验证矩阵:typecheck(双包)→ eslint → test → build:lib → check:bundle → check:governance → test:bench → test:e2e(Playwright chromium)→ grep 门禁 ≤ 24 - ✅ P3-C 版本对齐:
package.json0.0.0→0.0.101(对齐VERSION字面量,消除版本双源;发布时两处同步 bump,check:governance 门禁兜底) - ✅ P3-C 发布治理:新增
docs/guides/release-policy.md——弃用周期(@deprecated+@see替代 → 保留 ≥1 版本 → bump 时移除)、破坏性变更定义(删除/重命名/改签名,禁止无迁移路径)、版本单一来源与 bump 流程、体积阈值调整 SOP、发布前检查单
P3 验收命令(已落地)
pnpm --filter @nexa/gis-cesium run check:bundle—— build:lib 产物体积 CI 上限检查(实测 es 463KB / umd 373KB,gzip 约 123KB / 111KB,阈值带 ~15% 余量)pnpm --filter @nexa/gis-cesium run check:governance—— 导出面 / d.ts / 版本一致性 / 弃用审查,稳态输出「导出面 538 个 / 无删除 / 无新增」
lint 边界策略
- SDK lib(
packages/*/src/lib):error 强制清零;warning 为可接受存量,逐批收敛。新代码不得引入any、@ts-nocheck、@ts-ignore。 - 示例 App(包根
src及src/examples):允许保留运行示例所需的宽松写法,但不允许新增any。 - 测试文件:允许为 mock 使用类型断言,但禁止
as any——用as unknown as X显式表达"有意绕过类型"。 - 全仓唯一权威口径:
pnpm exec eslint . --ignore-pattern "**/dist-app/**"(dist-app为已忽略的应用构建产物)。
非目标
- 不包装 Cesium 每一个公共方法
- 不在 SDK 中承载战术仿真业务规则
- 不通过一次大规模重写解决历史问题
- 不把示例可运行等同于 SDK 可发布