在这篇文章中,我们将从 Web SDK 的接口层 Facade API 说起,讨论我们如何让 Web SDK 更易用,服务更广泛的开发者群体。
Web SDK 的设计目标
Web SDK 的架构设计从一开始就确立了以下几个目标:
- 通过插件系统灵活扩展非核心功能,支持用户个性化需求,并允许用户和社区开发插件。
- 高度可定制和可扩展,以满足复杂应用的需求。
- 为大型项目提供长期的可维护性和可测试性。
- 提供卓越的开发者体验。
为实现这些目标,我们引入了诸多机制,如插件系统、依赖注入和命令系统等。然而,这些机制的引入也带来了副作用:直接使用 Web SDK 的底层 API 开发门槛较高,可能会影响开发者体验——这一点与我们的目标“提供卓越的开发者体验”有所冲突。
幸运的是,“计算机科学中的所有问题都可以通过添加一个间接层解决”,我们引入了 Facade API 作为 Web SDK 的接口层,封装内部复杂度,为用户提供直观、易用的 API 接口。
简单易用的 Facade API
使用 Facade API 非常简便。如果你使用 Univer Presets,我们会自动为你创建 univerAPI,你可以立即开始开发:
const { univerAPI } = createUniver({ locale: LocaleType.ZH_CN, locales: { [LocaleType.ZH_CN]: mergeLocales(UniverPresetSheetsCoreZhCN), }, presets: [ UniverSheetsCorePreset({ container: 'app', }), ],})const sheet = univerAPI.getActiveWorkbook().getActiveSheet()const range = sheet.getRange('A1')range.setValue('Hello, Univer!')可扩展的接口设计
在 Facade API 的初期实现中,我们将所有功能集中在一个单独的 @univerjs/facade 包中。然而,这带来了以下这些问题:
- 即使用户没有引入某个插件,Facade API 中依然会出现该插件的代码提示,影响开发体验;
- 即使某个插件未被使用,由于 Facade API 的实现引用到了这些插件,因此底层实现仍会被引入到用户的构建产物中,导致额外的体积。
为了应对这些问题,我们对 Facade API 进行了重构。
首先,我们引入了接口类、扩展 mixin 和扩展机制。接口类对应原始的 Facade API 类型(如 FUniver、FWorkbook 等),都继承自 FBase,FBase 提供了 extend 静态方法,允许为这些接口类添加扩展 mixin。扩展 mixin 本质上也是 TypeScript 类,在继承接口类的基础上,添加新的方法以增强接口功能。
接着,我们将原来的接口拆分成多个功能模块。例如,FWorksheet 被拆解为以下几个文件:
- packages/sheets/src/facade/f-worksheet.ts:定义了
FWorksheet接口类。 - packages/sheets-ui/src/facade/f-worksheet.ts:实现了工作表 UI 扩展。
- packages/sheets-filter/src/facade/f-worksheet.ts:实现了工作表筛选扩展。
- packages/sheets-data-validation/src/facade/f-worksheet.ts:实现了数据验证扩展。
这种拆分方式使得每个文件只包含相应插件的 API,确保 Facade API 的功能模块化,降低了不必要的代码依赖。
我们还增强了 TypeScript 的智能提示功能。通过 declare 关键字,我们能告诉 TypeScript 接口类的类型已被扩展。例如,sheets-ui 包的代码如下:
FWorksheet.extend(FWorksheetSkeletonMixin)declare module '@univerjs/sheets/facade' { interface FWorksheet extends IFWorksheetSkeletonMixin {}}最后,我们为所有提供 Facade API 的包创建了二级入口点,允许用户通过二级入口点引入相应的 API。例如,使用 sheets-ui 的 Facade API 时,用户需要这样导入:
import '@univerjs/sheets-ui/facade'通过这些优化,我们成功解决了之前提到的两个问题:
- 只有在引入相应包的 Facade API 时,用户才能看到该包相关 API 的智能提示,避免了无关功能的代码提示;
- 同时,相关代码才会被打包进最终产物中,避免了不必要的代码冗余。
浏览器和 Node.js 同构
在一次重构中,我们严格规范了每个插件的运行环境,并尽可能复用浏览器和 Node.js 环境中的代码。目前,你可以在 Node.js 中运行 Web SDK,实现服务端读写和计算,甚至将它作为无头协同客户端加入基于 Web SDK 构建的协同编辑系统。
同样,Facade API 也可以在浏览器和 Node.js 环境中运行。唯一的区别是,一些包(例如以 ui 作为结尾的包)仅能在浏览器中运行,因此 Node.js 环境无法访问这些包所提供的 Facade API。不过,对于大多数场景,我们已经实现了一处编写,双端运行。
从嵌入式编辑器到完整的 Office 应用
Facade API 是 Web SDK 运行时面向应用的接口层。它让集成代码围绕工作簿、工作表、区域、文档和 UI 扩展来组织,而不必直接依赖内部服务。团队可以先实现一个嵌入式编辑器工作流,并在应用扩展时继续使用同一套 API 模型。
一个典型的集成项目可以分阶段扩展:
- 从 preset 开始,获取
univerAPI,并通过 Facade 对象实现内容操作。 - 只为 UI、筛选或数据验证等实际启用的功能引入对应插件的 Facade 入口。
- 在 Node.js 中复用不依赖浏览器的 API,完成无头读写与计算。
- 需要私有化实时协同或 Agent 入口时,再将运行时与 Univer Collaboration SDK 或 AI SDK 组合。
文档按 Web SDK、Server SDK 和 AI SDK 组织,分别介绍编辑器 API、协同与文件导入导出,以及 Agent 工作流。
持续提升 Facade API 的能力和易用性
我们的工程师持续为 Facade API 补充新能力。扩展机制允许每个包在对应功能旁提供 API,因此 Facade API 可以不断扩展,而不会让每个构建都引入无关实现。
我们也在不断提升 Facade API 的易用性。Presets 会注册所选功能需要的 Facade API,因此大多数集成项目无需逐一手动引入 @univerjs/xxx/facade 入口。我们还会继续完善指南和 API 参考,帮助开发者更容易地发现并使用已有能力。
今天我们深入探讨了 Facade API 的设计理念与实现过程,以及我们为持续提升其易用性和能力所做的工作。Facade API 在保留 Web SDK 模块化架构的同时,为开发者提供了易于使用的应用接口。随着 SDK 持续演进,它将帮助团队构建定制化的 Office 体验,从单一的嵌入式编辑器扩展到完整应用。
如果你还不知道 Univer Office SDK 是什么:它是一个同构的全栈开发框架,支持在浏览器端和服务端创建与编辑电子表格和文档。它帮助开发者将 Office 应用无缝集成到各种 web 系统中,全面提升用户交互体验。Web SDK 的核心架构和功能已在 GitHub 上开源。
作者:Wenzhao Hu,Tech Lead & Architect