小程序时代:如何跨多小程序开发协作?

导语

小程序端越来越多, 跨平台开发框架逐渐成为开发小程序的主流 , 目前跨平台开发有较多的开源解决方案 ,本文介绍一种简单而有效的方法,解决复杂小程序的跨团队开发协作,希望对你有所帮助。

背景

目前市面上小程序端越来越多,跨平台开发框架逐渐成为开发小程序的主流。目前跨平台开发有较多的开源解决方案,比如美团点评的mpvue、滴滴的Chameleon、凹凸实验室的Taro等,都做得比较好。这些框架帮我们解决了一处开发,多处运行的难点。但是在复杂的业务场景中,最终落地也存在着许多困难,需要自己解决。以58房产的新房业务小程序为例,实际的业务场景中,既需要有独立承载功能的“58同城新房楼盘精选”小程序,也有依托于其他流量平台的入口,比如在“58同城”、“安居客买房”等都有相应的新房业务,同时还有交叉的业务场景,比如在同经纪人业务结合的“安居客经纪人网店”中展示新房楼盘。这其中既有微信小程序,也有百度小程序。

这些业务有较多的共同点,比如基础功能逻辑是一致的,但是也存在很多差异性,比如商业逻辑,页面皮肤以及一些差异功能点。

新房首页对比

上图为三个小程序的首页对比,可以看到独立的小程序“58同城新房楼盘精选”集成了账号、城市、消息,在“58同城”和“安居客买房”这些能力则是依赖主体小程序。另外三个小程序之间还有些细微差别,但是楼盘相关的基础功能确是相同的。

一处开发多处运行的难题

作为业务方,我们希望业务代码也可以一处开发,到处运行。方案设计之时,我们的目标便是业务代码在同一仓库管理,同时方案具备较大的灵活性以适配各种环境。

在上述的背景下,实际开发中会遇到如下困难:

a) 各个小程序归属的开发团队不一样,使用的开发方案也不一样,有原生开发、wepy、Taro、mpvue等,意味着在源码层面是难以进行协作开发的;

b) 业务方与平台方之间是跨团队协作,需要尽量减少耦合,提高协作效率,同时避免相互影响;

c) 需要具备在各个小程序环境中的差异化开发方案;

d) 所有业务代码同一地方管理,意味着会有不必要的代码,需要有机制保障最终的打包结果大小是最优的;

e) 在不同平台小程序中,会依赖他们各自提供的基础能力,比如账户体系,消息等,这部分在各平台小程序中也存在着一定差异性;

f) 在不同场景下需要具备不同的接入方案,支持微信插件方式接入平台小程序,也要支持业务分包方式接入平台小程序。

整体架构设计

本方案基于Taro 1.3版本实现,其他小程序框架也可使用相同的方法做改造。在现有Taro基础上,无法支持到一份源码打包成多个同类型的小程序,因此在现有配置层进行扩展处理,并添加适配层,对于各个小程序不同点进行处理,最终实现直接打包到多个不同的小程序中,整体的架构主要分为四层: 

a) 配置层,用于解决在不同场景下的差异化,包括环境变量、主题样式、页面配置等;

b) 源码层,为具体的业务代码,常见方案,不做具体介绍;

c) 适配层,用于对接不同方案下小程序提供的接口,并牟平不同小程序提供的接口差异,为源码层提供统一的接口;

d) 打包层,与配置层相结合,用于打包最终交付结果;

以新房为例的架构图:

1.  配置层处理

1) 打包脚本配置

若要支持多小程序开发,需在package.json中增加scripts,用于区分环境。这里我们用的是cross-env这个包来设置,比如在打包58同城小程序时,加入环境变量WEAPPSOURCE=wbweapp。

{

"build:weapp": "taro build --type weapp",

"build:wbweapp": "cross-env WEAPPSOURCE=wbweapp taro build --type weapp",

"dev:wbweapp": "cross-env WEAPPSOURCE=wbweapp npm run build:weapp -- --watch",

}

然后在`config/index.js`配置defineConstants,用来配置一些编译时的全局变量供代码中使用,这里的配置会用于做打包的差异化处理。大部分的差异化配置,我们都放到了编译时来进行配置,有助于降低代码打包后的大小。其原理是通过webpack的define-plugin和uglifyjs-webpack-plugin两个插件配合来删除掉不可达代码,保证不使用的代码不会被打包。

config.defineConstants = {

WEAPPSOURCE: JSON.stringify(process.env.WEAPPSOURCE),

WBWEAPP: '"wbweapp"',0);">AJKWEAPP'"ajkweapp"',254);'>2) 差异性样式处理

在现有业务中,需要同时支持58同城和安居客两个品牌。二者之间页面结构是一致的,但各自有些主题色,我们将这部分差异抽取出来,变成Sass变量,然后整合至一个scss文件中,通过编译时引入不同的scss文件,来达到切换主题的作用。这里主要是配置`config/index.js`中的`config.plugins.sass.resource` 。

const sassConfig = {

wbweapp: '../wbweapp.scss',0);">ajkweapp: '../ajkweapp.scss'

}

config.plugins.sass.resource = path.resolve(__dirname,sassConfig[process.env.WEAPPSOURCE])

3)差异化页面处理

源码层中会包含所有场景下的全量页面,但每个场景所需的页面只是其中的一部分,需要做差异化处理。处理方法同上,略有差异点,通过编译打包时pages的配置不同,在`app.tsx`中的pages是决定引入哪些页面,我们通过传入环境变量找到对应的配置页面,实现按需配置打包。

`config/index.js`中配置:

const pagesConfig = {

wbweapp: ['pages/a'],0);">ajkweapp'pages/b']

}

config.defineConstants = {

PAGES: JSON.stringify(pagesConfig[process.env.WEAPPSOURCE])

}

`app.tsx`中配置:

class App extends Component {

config: Config = {

pages: PAGES,

}

}

2.  适配层处理

1) 差异化功能处理

功能的差异化处理,使用配置层定义的全局变量来做,伪代码如下:

import TabBar from '../components/tabbar';

export default _C Component {

render() {

return {(WEAPPSOURCE == WBWEAPP) && <TabBar/>}

}

}

这样写的话,当WEAPPSOURCE !== WBWEAPP时,TabBar组件不会被打包到最终代码中,wxml文件中TabBar的代码块也不会有。上面的import是不需要做特殊处理,打包时会分析依赖关系,没有被最终使用的文件不会被编译。

2) 接口统一封装处理

在各个平台方小程序中,通用功能都应该是统一管理的。比如用户信息,用户在58同城小程序内进行登录,各业务都能拿到统一的用户信息,而不是进入新房页面后再做一次新房的登录。这些功能,由平台方提供接口,供业务方调用。但各个平台存在差异性,这些差异性就由适配层做统一的封装,对业务开发提供一致的接口。

比如获取城市信息:

原理解析

通过以上介绍,已经解决了我们对差异化开发的要求,同时适配层将平台接口差异牟平,业务开发也不需要关心所处环境。大家可能比较好奇,所有的小程序代码都放在一起管理,最终打包出来的代码大小是不是最优的?主要是以下两点:

1)  在开发中注意利用条件编译来删除不必要的代码;

2)  在打包时做依赖分析及打包优化,业务层尽可能做更少的事情;

依赖分析优化工作主要是由@tarojs/cli包来完成的,简化后的流程图如下:

首先是解析入口文件`app.tsx`,通过两次语法转换,一次语法遍历,得到了依赖的样式文件、依赖的js文件、app的配置等,以及入口文件app.js。样式文件编译成最终的app.css,依赖的js文件,通过拷贝或生成,放到指定的目录中,app配置生成app.json。

两次语法转换是不一样的,第一次是通用的语法转换,比如jsx语法的处理。第二次是差异化的转换,会根据当前转换的类型是入口文件、页面文件或组件文件做一些特殊处理。第二次转换时使用了babel-plugin-danger-remove-unused-import插件,会删除不必要的依赖引入。上文提到的TabBar组件,虽然是被引入了,但在不需要的场景下TabBar组件就不会被打包。这里需要注意引入的文件,不应该存在副作用。

解析完入口文件后,会得到app配置的pages列表,页面文件列表循环通过同样的过程,得到页面的样式、js、配置等,以及所依赖的组件列表。

组件文件的打包过程跟页面是基本一致的,区别点在于组件会依赖其他组件。

理解了整个打包的流程,上面的问题答案就比较清晰了,不在pages配置里的页面是不会最终打包输出的,没有被依赖到的文件也是不会经过打包处理的。

与平台小程序集成

小程序最终的集成发布有三种方式:独立发布、插件集成、分包集成。

多个小程序的不同集成方案

1. 插件集成发布

如果是通过小程序插件方式集成,平台小程序可以将接口统一挂载到插件的变量中,二者就桥接上了。

插件的index.js设置(上文WBIndex即为引入的此文件):

module.exports = {

WB: {},254);'>平台小程序接口注入方法:

2. 分包集成发布

如果是分包集成的话,可以考虑将接口直接挂载在App中。

平台小程序接口注入方法(上文AJKIndex即为getApp()):

getApp().Common.getCityInfo = function() {}

采用分包集成方案的话需要注意,因为双方是在各自仓库下分别开发的,最终需要和到一起进行打包发布。目前我们采用的方案是配置`config.outputRoot`将结果代码打包到平台小程序仓库中,通过git管理,再由平台小程序做发布。

3.独立小程序发布

方案跟分包集成发布是一致的,不过API由自己提供,也挂载在App中,同时扮演了平台方和业务方。

实践经验分享

a) 小程序包依赖的json文件的处理,比如插件需要有插件配置文件`plugin/plugin.json`。可通过配置`config.copy.patterns`指定需要拷贝的文件或者目录来实现;

b) 小程序是插件和分包处理,在不同场景下的页面跳转路径是不一样的,但其实相对的路径是一致的,在于跳转前缀不同,可将页面跳转统一封装到适配层,根据环境变量适配不同的加上对应的前缀,当需要由插件切换到分包时,跳转部分仅需修改前缀,无需额外处理;

问题解决

前面提到一处开发多处运行的难题,得到了一一解决,整理如下:

a) 源码层面无法进行跨团队协作开发?

团队间分仓库开发,最终代码通过微信插件方式,或者分包方式进行集成。

b) 业务方与平台方之间的如何解耦?

通过统一的API,进行桥接,无其他耦合,API根据集成方式的不同,有不同的挂载方案。

c) 如何进行差异化开发?

针对样式差异化,配置差异化,功能差异化均给出了方法。

d) 如何保证打包结果是最优的?

尽可能的利用编译时的条件编译方法,排除不必要代码。

e) 平台方接口的差异性如何牟平?

增加了适配层,对业务提供一致的输入输出接口。

f) 支持不同平台小程序的多种接入方案?

支持了插件接入与分包接入。

总结与规划

本文介绍了在较复杂的小程序业务场景中,跨多小程序跨团队的协作方案,该方案帮助了新房业务在多小程序中的快速落地及迭代。

在实现了“58同城”小程序中的新房业务接入后,我们又做了“58同镇”的新房业务对接。只需要“58同镇”小程序提供一致的基础能力接口,即可轻松接入。

本文内容主要为业务经验积累,整体方案易于实施,带来的业务开发提效却是显著的,希望能帮助到大家。实际业务落地过程中,还有较多的细节需要处理,无法一一列举,欢迎提问或咨询。

文中仅介绍了业务在微信小程序的实践情况,实际上在百度小程序以及H5也已有相应落地实践,具备了一定的通用性,可以放心使用。

随着业务覆盖的范围越来越广,适配层会越来越复杂,不利于维护,更有效的方案是把业务实践总结为一套通用的接口标准,各个小程序按统一标准来实现API,业务方可以不关心所处环境的差异性,进一步提高跨团队开发的协作效率。 

参考文献

1. https://www.npmjs.com/package/cross-env

2. https://nervjs.github.io/taro/docs/config.html

3. https://webpack.js.org/plugins/define-plugin/

4. https://www.npmjs.com/package/uglifyjs-webpack-plugin

5. https://github.com/mishoo/UglifyJS2#compress-options 

作者简介

陈志庆 58同城资深研发工程师

叶春喜 58同城高级研发工程师

相关文章

概述 消息能力是小程序能力中的重要组成,我们为开发者提供了...
判断H5页面环境在微信中还是小程序中 用小程序提供的wx.mini...
wx.reLaunch和wx.navigateTo,wx.navigateTo的区别 2019-03-...
微信小程序如何从数组里取值_微信小程序 传值取值的几种方法...
H5项目接入微信授权登录,通过 UA 区分微信还是普通浏览器:...
微信小程序获取data-xx=&quot;&quot;属性的值,自定...