导读:从顺丰的渠道场景出发,介绍技术研发侧在多渠道多场景下的前端SDK最佳实践,通过实际项目场景论证技术方案落地,包括跨端演进、工程化实践、可视化构建等实践,以及(前端)研发团队在技术创新与业务交付平衡调节点上的探索过程。
一、建设背景
1、渠道场景驱动下的前端中间件
每天数千万的物流订单从顺丰的各个渠道下单,这些订单可能是顺丰速运App、顺丰官网、顺丰速运+小程序,可能是来自厂商快应用、华为小程序、抖音小程序,还可能是来自合作厂商的聚合下单服务、合作商家点的快递服务,也可能是线下的扫码寄件、网点自寄、小哥寄件。而作为这些面客渠道的研发团队,是如何通过技术设计快速的把应用交互呈现到用户手中,如何通过技术设计去兼容这些复杂多样的场景以及支持更多个性化的需求扩展,又如何对内去开放技术窗口协同建设。
在前端研发领域,视图层与业务服务层的交互,基本上就是各端一对一、点对点实现,以及小规模的、局部的中间层设施建设。从最初的公共函数复用到现今作为各端能力连接到H5项目、业务逻辑复用到渠道平台、后端服务分发到前端应用的“前端中间件”。摒弃了纯技术封装或者全BFF转发的模式,而是根据渠道业务+前端技术的场景化集成方向。
同时,这种思路和模式在中大型前端团队面向领域技术建设中具有技术创新、业务沉淀的能力,是团队(特别是大前端团队)找到技术探索与业务交付的平衡调节点。统一接口平台(UIP)就是这样的场景和发展中落地的前端SDK最佳实践,成为前端团队在业务迭代和技术积累过程中沉淀的产物,并持续服务于司内多项目的前端SDK。目前,UIP正运行在顺丰集团官网、顺丰速运App、顺丰扫码寄件、顺丰微信公众号、顺丰速运+微信小程序。
2、UIP建设主要历程
- 从最初的单体项目多页面复用的公共方法,如XMLHttpRequest网络请求、URL参数解析、日期格式转换、浏览器userAgent解析、电话邮箱正则等封装到utils.js。
- 到跨平台的API差异抹平,UIP根据宿主环境处理兼容和适配,对外提供统一的get/set方法,让研发更聚焦业务逻辑的实现。
- 再到多渠道公共业务的抽象,包括领域内标准的业务常量和数据结构、公共业务逻辑的封装、物流行业基础数据等,以实现跨项目的复用。
- 针对业务聚合场景入口、营销活动落地页,一般仅运行在App&公众号&小程序等容器下,归集了Webview场景下的通讯、路由、扫描、打印等原生API方法,解决目标H5页面与宿主的交互问题。
- 业务项目中沉淀的技术方案/最佳实践,或外部供应商的解决方案,通过UIP包装后提供给渠道项目调用,避免重复建设,同时也把技术和业务隔离,用技术解决业务问题,而不在业务逻辑中封装技术代码。
- 微服务体系中,外部应用对接顺丰后端鉴权、对接开放平台授权、对接渠道API能力,则把整个流程中涉及中转和调用打包到uip-microservice-sdk,全面提升接入效率。
- 自然而然的,场景定制化SDK是发展的必然。内部项目可按模块按需引入,而外部项目则可通过可视化构建定制SDK。
图1: UIP建设历程
二、技术体系
1、跨端技术演进
UIP经历了从依赖注入的多端适配方案到运行时判断宿主环境的方案的演进,这一过程主要围绕API抹平、代码复用以及开发者心智负担的优化展开。
1.1 基于依赖注入的多端适配
——手动引入、依赖注入
表1:不同宿主环境的子包
- API 静态拆分:针对不同宿主环境,将核心功能拆分为多个子包。
- 使用方式:项目开发者需要手动引入自己所在环境所需的功能包,并将其挂载到统一的全局对象中,业务开发过程中之需要访问统一的全局API标识实现跨端能力的注入。
- 优点:
- 只包含目标宿主环境的代码,打包体积更小;
- 代码逻辑清晰,避免了运行时动态判断。
- 缺点:
- 心智负担高:开发者需要明确自己处于哪个宿主环境,并手动选择正确的功能包;
- 维护成本高:每个端的适配包需要单独管理,版本更新较为繁琐。
1.2 基于运行时判断的多端适配
——运行时动态判断宿主环境
- API 动态抹平:
- 在SDK初始化时,自动检测当前的运行环境(H5、小程序、App等)。
- 统一封装不同端的API,开发者无需关心底层实现,只需调用统一的接口即可。
- 使用方式:开发者无需手动引入环境适配包,按需引入基础能力API(如网络请求、本地存储等),SDK会自动识别当前宿主环境并调用正确的API。
- 优点:
- 开发体验更好,开发者只需调用SDK暴露的统一API,无需关注底层适配逻辑;
- 维护成本降低,不同端的API统一管理,不再需要开发者手动引入。
- 缺点:
- 由于运行时构建,打包体积变大,所有环境的API都可能被包含进最终构建包。
1.3 端侧按需构建
作为SDK的提供者,我们深知SDK的体积大小会在很大程度上影响项目侧是否引入,为了解决UIP运行时构建的体积问题,我们通过插件的方式提供了UIP按需构建的能力。我们在设计request、storage此类多端API时,会在文件结构上实现不同宿主API的拆分,通过我们的按需构建插件在编译时自动完成当前宿主API路径的转换,在开发者无感知的前提下完成非宿主API的shake。
2、工程化深度实践
随着UIP的持续迭代,我们的工程化架构也经历了从0到1.0再到2.0的演进,我们也在不断尝试优化包管理、版本发布、构建速度等多个方面。
2.1 早期项目选型及目录结构
- 依赖管理的挑战:UIP SDK 由多个层次的package构成,从 底层的跨端 API 到 工具包、业务包,它们之间存在层层依赖且互相依赖的关系。例如:
如果uip/env更新,所有依赖它的包都需要同步更新版本。手动管理这些依赖的版本极为复杂,容易出现版本不匹配、回滚困难等问题。为了解决这个问题,我们选择了 Lerna 进行依赖管理。
- Lerna + Monorepo架构:
UIP最初采用 Lerna + Monorepo 作为项目管理方案,具备以下特点:
- 自动版本管理:更新某个包后,Lerna 会自动处理相关依赖的版本升级;
- 单体仓库管理:所有包在同一个代码库中,开发者可以直接查看所有组件的代码,不需要多个仓库来回切换。
- 完全扁平化的package结构。
表2:扁平化的package结构
2.2 工程化链路优化
随着项目的发展,我们发现早期的工程化体系存在以下问题:
因此我们在工程化链路方面做了如下优化:
- 引入swc 替换 tsc
- ts 编译提速 5 倍以上:swc 采用 Rust 实现,比 ts 原生编译器快得多;
- 支持 tree-shaking:swc 直接支持 ES 模块优化,避免打包冗余代码;
- 与 babel 兼容:可以无缝替换 babel 进行代码转换。
- 引入 Turborepo
- 智能任务编排:Turborepo 能够自动分析任务之间的依赖关系,并 并行执行 无依赖的任务,大幅加速 CI/CD;
- 构建缓存:相同代码的 build 任务不会重复执行,而是直接复用缓存,极大提升构建效率;
- 支持 Monorepo:Turborepo 仍然保留了 Monorepo 的管理能力,适合多包管理。
- 引入Changeset
由于Turborepo本身并不具备版本控制和依赖管理的能力,而这又作为UIP SDK集成的重要一环,我们选择使用Changeset工具:
-
- 自动追踪变更:Changeset 允许开发者在代码提交时手动记录版本变更,避免不必要的全量版本升级;
- 精细化版本控制:开发者可以选择 minor / patch / major 级别的变更,保证版本演进的稳定性;
- 无缝集成 Turborepo:可以结合 Turborepo 进行批量发布,提升发布效率。
图2:UIP技术全景图
2.3 包聚合度提升
在 UIP 1.0 版本中,我们采取了完全扁平化的包结构,但这一方式在实际开发中存在如下问题:
- 功能包过于分散:开发者需要 频繁关注多个小包,不利于维护;
- 相近功能的包没有归类,不利于开发者快速找到所需 API。
在 UIP 2.0 版本中,我们对 目录结构进行了优化,将同类型的 API 进行归类,例如:
优化点:
- 减少开发者心智负担:开发者不再需要在多个 utils-* 包之间切换,utils 统一管理所有工具函数;
- 缩短 API 访问路径:开发者可直接从 core 和 utils 访问 API,而不是 utils-common, utils-date 等多个独立包。
此外,我们在 package.json 中利用 exports 字段 进行更精细的导出控制,支持按需引入:
2.4 集成化IDE插件
在SDK的演进过程中,我们始终关注如何提升开发者体验,降低开发者心智负担,简化开发调试流程也因此成为重要的一环,为此我们针对UIP的最新架构研发了相应的vscode扩展插件。
其功能主要包括:
- 开发&构建&发布命令集成:一键执行dev、 build、publish 命令;
- 代码模板:快速生成跨端SDK package模块代码;
- 链接调试:内置yalc调试工具,优雅解决npm link导致的种种问题。
3、可视化SDK定制
3.1 平台方SDK的传统思维
——不是你要什么,而是我有什么
3.2 定制化SDK思维
——你需要什么,我就给你什么。
作为UIP SDK的提供方,我们认为用户的诉求在于只希望按需引入对自身真正有用的API,因此我们需要有这样的平台和能力提供给用户:
- 能够归集UIP当前提供的各项能力,如基础包、工具包、业务包等;
- SDK的定制化粒度应该在API级别,最大程度保障定制SDK的精简;
- 作为用户、作为项目接入方,基本能够无成本的完成SDK的定制和对接;
- 用户定制的SDK有版本的管理和维护,遇到异常能够回滚。
3.3 可视化SDK构建平台
基于上述的问题我们提供了一套完善的解决方案——可视化SDK构建平台。
图3:可视化构建流程
图4:可视化构建控制台
三、场景运用
1、微应用定制SDK
微应用体系是基于顺丰自主渠道为平台的异业合作接入体系,外部应用接入需要调用客户端本身的宿主能力、对接开放平台能力、顺丰平台的生态能力等,基于此,UIP归集了微服务对接链路中的各项能力,并结合UIP可视化构建能力,实现微应用的标准化、定制化接入。
图5:微应用场景 & SDK交互示意图
2、统一API调用示例
客户端每一端缓存策略和机制略有不同,经UIP抹平后,可使用同一API在不同端调用缓存能力,同时支持定制化的缓存策略。
3、标准化路由转发
在平台型应用中,同一个页面往往运行在不同的宿主中,需要适配各自差异化的跳转方式、路径结构和传参规则。为了应对多宿主、多路径、不确定调用环境的复杂性,我们在前端 SDK 层设计了路由转发机制,作为一个中间适配层,屏蔽不同宿主路径差异,实现统一调用体验。
路由转发的核心思路就是SDK 通过内部定义的 逻辑路径(虚拟路由),自动映射并转发到不同宿主下实际的页面地址或跳转方式。
四、未来畅想
UIP在前端项目研发过程中,更多的是承担中间件SDK的角色。在未来(当下即未来),公司的各渠道、各领域全面中台方向之后,可能会有更多平台类的、业务类的、体验类的、监控类的、安全类的SDK,承担更多的角色和任务。特别是AI浪潮之下,大模型落地场景中必不可少的一环就是连接外部能力的SDK。
所以,AI辅助编程,也有可能是,辅助AI编程。