前言
现在是北京时间 2024 年 6 月 20 日 20:46:16 星期四,今晚,猎个痛快 聊个痛快。
最近这 10 天在搞 SDK 的设计与开发,从目前的状态来看,显然已经步入狂人境(每天晚上回家基本搞到 12 点以后,周末不打烊 )😅
从 git 提交记录来看,我显然也不是经常这样搞,可能是好久没有找到动力源泉了吧,同时我也渐渐明白了为什么张朝阳说多睡无意义,因为任重而道远啊 👏
正文来了
SDK 设计的核心要素
柜架设计要比想象导复杂,并不是说只把功能开发完成,能用就算大功告成了,这里面还有很多学向。比如,我们的 SDK 应该给用户提供哪些构建产物?产物的模块格式如何?当用户没有以预期的方式使用 SDK 时,是否应该打印合适的警告信息从而提供更好的开发体验,让用户快速定位问题?当 SDK 提供了多个功能,而用户只需要其中几个功能时,用户能否选择关闭其他功能从而减少最终资源的打包体积?上述问题是我们在设计 SDK 的过程中应该考虑的。
1.提升用户的开发体验
在 SDK 设计和开发过程中提供友好的警告信息至关重要,如果这一点做的不好,那么很可能会受到用户的抱怨,始终提供友好的警告信息,不仅能够帮助用户快速定位问题。节省用户的时间,还能够让 SDK 收获良好的口碑,让用户认可 SDK 的专业性。
2.控制代码的体积
SDK 的大小也是衡量 SDK 的标准之一,在实现同样功能的情况下用越少的代码,最后浏览器加载资源的时间也就越少。
3.做到良好的 Tree-Shaking
什么是 Tree-Shaking 呢?在前端领域这个概念因 rollup.js 而普及,简单的说 Tree-Shaking 指的就是消除那些永远不会被执行的代码。也就是排除 dead code,现在无论是 rollup.js 还是 webpack 都支持 Tree-Shaking。
想要实现 Tree-Shaking 必须满足一个条件,即模块必须是 ESM(ES Module),因为 Tree-Shaking 依赖 ESM 的静态结构。另外如果一个函数调用会产生副作用,那么就不能将其移除。什么是副作用?简单的说副作用就是当调用函数的时候会对外部产生影响,例如修改了全局变量。但是到底会不会产生副作用,只有代码真正运行的时候才能知道。想要静态分析哪些代码是 dead dode 很有难度,所以要合理的利用*#__PURE/__*/注释,其作用就是告诉 rollup.js 该函数的调用不会产生副作用,可以放心的对其进行 Tree-Shaking。
4.应该输出怎样的构建产物
不同类型的产物一定有对应的需求背景,因此我们从需求讲起,首先我们希望用户可以直接在 HTML 页面中使用 script 标签引入 SDK 并使用。 如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DEMO</title>
<link href="/plugins/Cesium/Widgets/widgets.css" rel="stylesheet" />
<script src="/plugins/Cesium/Cesium.js" type="text/javascript"></script>
</head>
<body>
<div id="play"></div>
<script type="module" src="/main.ts"></script>
</body>
</html>
因此需要输出 IIFE 格式的资源,通常我们会输出 UMD 格式的资源,UMD 是一种尝试在 AMD(Asynchronous Module Definition,异步模块定义)和 CommonJS 之间提供兼容性的模块定义方式。它还支持在浏览器环境中作为全局变量使用。
随着技术的发展和浏览器的支持,现在主流浏览器对原生 ESM 的支持都不错,所以用户除了能够使用 script 标签引用 UMD 格式的资源外,还可以直接引入 ESM 格式的资源。而引入了 ESM 格式的资源后,用户在进行项目打包的时候,打包工具会对其进行 Tree-Shaking 处理。这样会使构建的资源更小。 如:
<script lang="ts" setup>
import { onMounted } from 'vue'
import { Earth } from 'xxx'
onMounted(async () => {
const earth = new Earth('cesiumContainer')
})
</script>
<template>
<div class="h-full">
<div class="absolute z-1 bg-red px-10px py-4px">本地调试 / Resources from CDN or Local static</div>
<div id="cesiumContainer" class="h-full" />
</div>
</template>
5. 特性开关
在设计 SDK 时,SDK 会给用户提供诸多特性或功能,例如我们提供 a、b、c 三个特性给用户同时还提供了 a、b、c 三个对应的特性开关。用户可以通过设置 a、b、c 为 true 或 false 来代表开启或关闭对应的特性,这将会带来很多益处。对于用户关闭的特性,我们可以利用 Tree-Shaking 机制让其不包含在最终的资源中。该机制为 SDK 设计带来了灵活性,可以通过特性开关任意为 SDK 添加新的特性,而不用担心资源体积变大。同时当 SDK 升级时,我们也可以通过特性开关来支持遗留 API,这样新用户可以选择不使用遗留 API,从而使最终打包的资源体积最小化。
6. 错误处理
错误处理是 SDK 开发过程中非常重要的环节。SDK 错误处理机制的好坏,直接决定了用户应用程序的健壮性,还决定了用户开发时处理错误的心智负担。
7. 良好的 TypeScript 支持
TypeScript 是由微软开源的编程语言简称 TS,它是 JavaScript 的超集,能够为 JavaScript 提供类型支持,现在越来越多的开发者和团队在项目中使用 TS 使用,TS 的好处有很多,如代码编辑器自动提示在一定程度上能够避免低级 bug,同时代码的可维护性也会更强。因此对 TS 类型的支持是否完善也成为评价一个 SDK 的重要指标。
Galaxy-Earth 设计方案
Galaxy-Earth 是基于 TS 语言与 Cesium 可视化引擎,可支持多行业快捷搭建二三维可视化场景的轻量级地图渲染类库。SDK 以中间件的形式赋能原生引擎,做为可视化引擎和空间数据可视化业务之间的粘合剂,包含轻量化与高效能的 Vue3 通用组件及 TS 类库。业务平台可借助 SDK 通过简洁的 API 或组件,在不同的业务平台中轻松创建丰富的二三维地图可视化效果。
除了上文所列出的 SDK 设计的核心要素外,Galaxy-Earth 在设计上还有如下原则:
- Galaxy-Earth SDK 不对 Cesium 源码做任何修改,跟随 Cesium 版本持续迭代,只做通用且抽象化的高级功能。
- Galaxy-Earth SDK 分为 core 和 components 两个包,core 为抽象引擎部分,不会包含任何面板交互功能但可支持视图组件的 mixin。components 以 Vue3 为基础,编写通用视图组件并可在 项目中与 core 结合,且支持自由插拔。根据上述分包,总体将拥有良好的 Tree-Shaking 效果。
代码结构如下:
galaxy-earth
├─ 📁.eslint-config
├─ 📁.vscode
├─ 📁core─────────────────── ⭐核心SDK类库
├─ 📁components───────────── ⭐Vue3通用视图组件
├─ 📁docs─────────────────── ⭐VitePress静态文档
├─ 📁play-local───────────── 测试-未打包直接引入
├─ 📁play-mjs─────────────── 测试-打包后模块化引入
├─ 📁play-umd─────────────── 测试-打包后Script标签引入
├─ 📁types
├─ 📄.editorconfig
├─ 📄.gitignore
├─ 📄.npmrc
├─ 📄bundle.sh
├─ 📄eslint.config.js
├─ 📄package.json
├─ 📄pnpm-lock.yaml
├─ 📄pnpm-workspace.yaml
├─ 📄README.md
└─ 📄tsconfig.json
项目整体以 Monorepo 形式搭建,重点模块为上方标星部分。
core ── 核心 SDK 类库
core 部分,是整个项目的灵魂与基石。在 core 引擎的设计上,对外提供 userConfig 模块以及 classes、utils、shortcuts 三种形式的功能调用。
- userConfig: 用户可通过该模块进行一些自主配置,如:自定义 Cesium 渲染异常处理并取消红框弹窗
- classes: 由一个核心超集管理类与各种功能类组成,在核心超集管理类的内部形成各种数据、特效的管理闭环,如图层、图元、环境特效等管理。
- utils: 由各种常用函数组成,在 SDK 内部使用的同时,可对外暴露提供使用。
- shortcuts: 由各种 classes 与 Cesium 原生功能衍生而出的快捷功能,无需与核心超集管理类集成即可直接调用,如果项目不关心数据的管理,可直接单独引入 shortcuts 完成功能开发,不仅更加方便快捷,而且在 Tree-Shaking 上也优化显著。
components ── 通用视图组件
在 components 组件的设计上,没有沿用 Cesium 官方 widgets 的前端三剑客写法,而是采用了目前主流的 Vue3 进行组件开发,可更加方便的复用第三方组件库开发 SDK 所需的各种通用视图组件,这种转变带来了诸多优势,不仅提高了组件的复用性和可维护性,还极大地提升了开发效率和用户体验。
docs ── VitePress 静态文档
docs 模块基于 typedoc 的能力可自动根据 core 中的代码生成 API 文档,另外可自由编写入门指南及组件文档,同时 docs 模块可进行功能在线演示及代码查看复制。
欢迎评论
如果大家有什么看法或问题,也可以关注我的公众号哦 —【诗传千古地负海涵】 🌟