Titan解决方案融合引擎
随着微盟微信小程序业务的拓展,尤其是微盟OS(SaaS新云2.0)的建设,已有的微盟微信小程序SaaS新云1.0开发框架逐渐难以支撑业务形态的拓展。为了解决这个问题,我们设计了允许业务通过开发产品包的模式在新的DSL(Domain Specific Languages)下进行开发。
以下为整体设计架构:
我们之所以做出这样的设计是因为:
1、由于微信小程序分包之间无法相互存在种种限制,为了开发便捷,在SaaS新云1.0的微信小程序原生开发框架下业务模块依赖往往放置在主包内。
我们需要一种开发模式,让业务开发人员明确的知晓使用的依赖是某种公共能力、还是实际上只是当前业务模块所需要的能力。在开发过程中就从技术层面做出合理规划,梳理业务架构逻辑,把相似功能的页面、组件归纳如一个package,并且通过声明的方式,明确其使用的公共能力(platform)。
2、由于在微信小程序内任意一个模块内都可以对全局的对象(如wx、app等)内的数据、函数进行挂载和修改,在开发层面各个业务模块都有可能干扰小程序整体、或其它模块。
我们需要一种开发环境,能够有效的隔离每个业务模块的运行时环境。在package或platform内,开发者通过声明的方式明确其对外暴露的页面、接口(如果是platform,还有组件)。
3、由于在一个微信小程序分包内调用一个还未加载的分包时会发生报错,业务开发往往在调度另外一个分包内的能力时,往往需要做复杂的逻辑编写。
我们需要一种开发框架,能够自动处理分包的加载和接口调度时完成对所依赖的目标分包的调度机制。
4、由于组件内的结构文件(wxml)和样式文件(wxss)在微信小程序内只能被当前分包内的其它组件或页面引用,或安置在主包内是可以被所有分包引用,无法放入主包的大型组件只能通过手动复制粘贴代码的方式在不同分包内进行维护。
我们需要一种代码编译器,能够在编写过程中维护一套组件代码,在构建过程中自动通过依赖的声明将组件代码分发到所需的各个packages内。
5、由于微信小程序的主包空间有限,而微信原生的导航栏(tabBar)必须声明为主包内的页面时才能够展示,在SaaS新云1.0的微信小程序原生开发框架下,面对日益增加的主包,只能选择放弃微信原生的tabBar,而开发一套满足和微信原生导航栏体验、性能、功能相类似的导航栏技术门槛很高,因此实际产品形态中我们见到的替代品往往是在页面底部加上样式和主导航类似,但是点击后实际上是打开新页面的“伪导航栏”能力。
我们需要一种代码编译器,能够通过声明商户选用的导航栏内容,自动分析提取对应的页面,如果可以在主包内放置下这些页面,则自动编译为含有微信原生导航栏能力的小程序,否则,我们将构建一种虚拟环境,将对应的页面放入分包内,再通过构建一个虚拟的导航栏页面容器,将原有的页面构建为组件嵌入该页面内,并且实现原生导航栏中包括下列刷新、页面切换后的生命周期、保留页面滚动位置和状态、允许使用位置滚动(scrollTop)和固定定位(position:fixed)等一系列功能,进而来实现和原生导航栏相同的用户体验。
6、由于原有的各个解决方案的代码是一个“死”的功能集合,因此在业务架构设计保留了大量给业务开发将各个模块耦合的机会(很多情况下耦合可以提升开发、运行时效率),这就导致如果我们需要灵活组合各个业务功能时存在很大的难度。
我们需要一种开发标准和工程化能力,在开发过程中明确定义每个模块所暴露的接口和获取依赖的逻辑,并且在打包过程中将各种依赖、组件根据合适的标准分发至小程序的主包或模块所对应的分包内。这样我们才能做到自由的模块插拔从而实现微盟商家操作系统的构思。
7、由于早期SaaS微信小程序和之后的多渠道开发框架都是将工程能力暴露给了开发者,这就导致不同现有开发团队的工程化能力都多少被修改。不同的构建方式和配置逻辑,导致多个不同开发团队之间的模块难以同时被处理,并且对统一的持续集成、发布系统也造成了效率上的负担。
我们需要一种开发命令行工具,仅允许开发者按照标准的配置逻辑对项目进行工程化配置。同时我们采用了基于go编写的esbuild作为底层编译器(较目前常用的webpack 5构建速度提升150倍)对项目进行构建。这样大幅度提升了编译效率,为支撑数万商户独立构建、发布做出了准备。
框架内部逻辑说明
Titan框架的抽象思路和核心构建逻辑是通过现在平台(公共、中台)能力对最为原子化的通用能力进行提取,得到最为精简、高效的代码以后,将这些必不可少的通用能力放入platform-main(对应微信小程序的主包)中。
同时,在一些多个不同业务模块能可以被使用的页面、组件、和APIs中,我们添加了一种platform-extensions的设计逻辑。这些能力由平台能力团队维护(如装修能力组件),但是其代码可能比较沉重,如果放置在主包,可能会导致主包内容过大。并且,这些能力可能并不是每一个业务模块都需要依赖使用。因此,我们的设计是,它们会在构建编译时,通过业务模块对其的依赖,按需打包入对应的业务分包内。如此,就能够对通用的代码逻辑进行统一的管理,但是又避免了主包内珍贵的空间被这些不高频使用的通用逻辑所占据。也因为这些逻辑并不是放在主包内,而是进入分包以加载所特别声明的依赖,故而提升了代码逻辑的加载执行效率。
最后,由于微信小程序内,原生底部导航栏(tabBar)仅可承载主包内的页面,但是我们在开发过程中并不能得知商户自由选择的底部导航上的页面(还有其对应依赖的组件、APIs等)是否会导致主包内的代码超过限制大小(2MB)。因此我们会在构建编译过程中,对商户所选择的底部导航所需的页面进行提取和分析,如果编译结果是底部导航所需的页面和其它主包内的文件之和能够小于或等于限制大小,则自动修改app.json的配置,并把代码打入主包内;如果编译结果是底部导航所需的页面和其它主包内的文件之和大于限制大小,则我们会通过一个虚拟的运行时环境构建一个和原生底部导航栏提议相似的多页面切换环境,并且将该环境放入一个单独的分包内,然后将各个所需的页面合并打入该分包。至此,我们就能够实现一个完整的支持自定义业务能力插拔、动态生成底部导航的微信小程序编译和构建流程。
除了代码的构建以外,微信小程序还对代码中使用的能力进行了配置化声明。比如,如果需要调度获取地理位置的API,则需要在app.json内声明scope.userLocation,并附上获取地理位置时弹窗内的文案。这样,各个平台、业务模块可能都会需要在app.json内声明相同的选项(如地理位置),但是其对应的配置内容又可能不相同。为此,我们设计的逻辑是,有一个项目全局的配置,如果该配置存在时,则优先采用全局的配置选项,如果全局配置不存在时,为了保障对应的模块能够正常运转,我们会优先取平台能力的声明,再取业务模块的声明。如果同时存在多个平台或业务模块的声明,则优先取构建优先级较高的模块(构建优先级为全局配置内的数组,因此优先级是唯一的,不存在优先级冲突)。
最后,我们来看下在构建、运行时部分Titan框架的简易流程。
其中编译时的逻辑如下:
运行时逻辑如下: