Skip to content

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.jsparserOptions.project 覆盖 gis-cesiumcommon-data 两个包的 tsconfig.app.json,并忽略 vite.config.ts / worker.ts(消除跨包解析错误)。

定位

gis-cesium 是 CesiumJS 扩展 SDK,不是替代 CesiumJS 的全量引擎,也不是禁止上层接触 Cesium 的防腐层。

SDK 的价值集中在四类问题:

  1. 跨产品可复用的 GIS 能力
  2. 多个 Cesium 对象协同形成的高阶能力
  3. 事件、DOM、GPU 和场景对象的资源生命周期
  4. 性能策略、版本差异与私有 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 error

P0 遗留(转入 P1~P3)

  • 存量 284 个 lint warning 未清零(不阻塞发布)
  • 材质模块的 Cesium.Property 私有断言已统一迁移至 propertyUtils 助手;其余私有 API 访问待 P2 适配层收口

P1:统一扩展契约 ✅ 已完成

  • 生命周期契约:新增 core/lifecycle.tsDestroyable / Lifecycle<THost> 类型并公开导出;ConnectionManager / LayerManagerdestroy() 幂等守卫,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) 函数式助手共用同一实现,消除幽灵 API viewer.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 缓存检查,与 registerAllreg() 跨路径幂等(G1);Material1() 加缓存守卫 + 宿主全局存在性检查,并在 viewerToolsMixin 安装时前置调用(G2/G3);新增 registerAll.spec.ts、扩展 Material.spec.tsviewerToolMixin.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 处 assertExcavateViewer

P1 遗留(转入 P2)

  • Mixin 返回对象形状统一activate/deactivate/clear/destroy 词汇表统一(positionPickadd/removeAllgridshow 是数据属性等),跨 ~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.tsany 精化为具型形状(DefaultView/FrameState/Context/UniformState 等),补 Framebuffer/Texture 构造器 + createPropertyDescriptor + Cesium3DTilePass 命名空间;viewerOffscreenRenderMixin 5 处、createFrameBuffer 2 处冗余强转清零,_hdr → 公开 scene.highDynamicRange 等价替换(其余 _defaultView/_frameState/... 无公开替代,保留 shim 具型)
  • P2-B1:Property 静态量迁移(net-zero)——viewerToolMixin Material1 的 isConstant/equals/getValueOrClonedDefault 迁至 propertyUtilscreatePropertyDescriptor 入 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 → 24test:e2e 6 用例通过(案例 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 还原;同时修复 AirViewer credit 处理用 appendChild(textNode) 替代 innerHTML= 覆写(innerHTML 清空 CreditDisplay 内部子容器会使 Cesium 销毁时 removeChild 报 "not a child")
  • 散点收口EntityDetectEffectnull as unknown as Entity ×2 → source/target: Entity | null(grep 26 → 24)
  • 测试extend.spec.ts(8)+ textureManager.spec.ts(8)+ plugins.spec echart 按次重注册(+1)+ e2e 用例 f(销毁协议 + Camera 同一性还原 + 重建);test 280 passed / 41 files,test:e2e 6/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.createBatch 500 条、removeAll 归零、1000 new 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 === 0scene.primitives.length === 0(跨 viewer 资源归零;内存字节不断言——SwiftShader 下 performance.memory 噪音大,资源计数是确定性代理)
  • P3-B 体积门禁scripts/check-bundle.mjscheck:bundle)——es raw ≤ 520KB / gzip ≤ 140KB、umd raw ≤ 420KB / gzip ≤ 125KB、css ≤ 20KB;PNG 底图资产(15.45MB,AirViewer 默认底图)仅报告不设限(设计决策,后续可考虑外置)
  • P3-B 治理门禁scripts/check-governance.mjscheck:governance)——导出面 manifest 与 public-api.baseline.json538 个公共导出)比对:删除导出 exit 1(禁止无迁移路径破坏)/ 新增提示 / kind 变化 warn;d.ts 完整性(入口非空、export 链可解析、package.json 声明的类型入口可解析);版本一致性断言(package.json.version === NexaCesium.tsVERSION 字面量);@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.json 0.0.00.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(包根 srcsrc/examples:允许保留运行示例所需的宽松写法,但不允许新增 any
  • 测试文件:允许为 mock 使用类型断言,但禁止 as any——用 as unknown as X 显式表达"有意绕过类型"。
  • 全仓唯一权威口径:pnpm exec eslint . --ignore-pattern "**/dist-app/**"dist-app 为已忽略的应用构建产物)。

非目标

  • 不包装 Cesium 每一个公共方法
  • 不在 SDK 中承载战术仿真业务规则
  • 不通过一次大规模重写解决历史问题
  • 不把示例可运行等同于 SDK 可发布