多重影分身:一套代码如何生成多个小程序?
前言
影分身术,看过火影的都知道,一个本体,多个分身。
大家肯定要问了,那小程序开发跟影分身术也能扯上关系?没错,那自然就是:一套代码,多个小程序啦。
各位先别翻白眼,且听我细细说来。
如今小程序发展如日中天,再加上微信的力推,很多公司的业务也都慢慢的转向小程序,这让我这个安卓开发,也不得不开始了小程序开发之旅。
然而随着公司的发展,客户越来越多,核心功能相同的小程序,需要上架多个小程序分别给不同的客户使用,每个小程序之间又存在这一小部分的定制化,比如界面展示的不同、小功能的差异等等。
这可让我这个刚接触小程序开发的前端菜鸟抓狂了,每个小程序复制一份代码出来,然后做定制化的修改?这岂不是如果哪天核心业务有改动,我得对每套代码分别改动一次?不行,即使是菜鸟,对这种弄出多套重复代码的行为也是无法容忍的!
于是,就有了针对这种场景下的一个解决方案:给小程序开发来个影分身术。
Github地址:https://github.com/BakerJQ/WeAppBunXin
该项目基于Taro框架,由凹凸实验室开源,非常感谢他们的努力付出。
之所以选用Taro,主要是因为它采用React语法标准,而本人之前有过ReactNative开发经验。
由于本人接触前端开发时间不长,文中若出现了错误或者有更好的方案,欢迎各位包容和指正,万分感谢。
影分身之基础配置
影分身的能力,主要来源于Taro所提供的编译能力,所以需要对Taro的编译配置和编译配置详情有所了解。
我们先来看看配置的相关文件目录:
config目录为Taro初始化后的默认配置目录,图中蓝色框框内的三个文件(dev、index、prod)为默认生成的配置文件,剩下的文件,则为分身所需的配置。图中配置了三个分身,我们以channel1为例,config是该分身的一些配置,project.config.json就是该分身小程序的基本配置,如:
{ \u0026quot;miniprogramRoot\u0026quot;: \u0026quot;./\u0026quot;, \u0026quot;projectname\u0026quot;: \u0026quot;channel1\u0026quot;, \u0026quot;description\u0026quot;: \u0026quot;channel1\u0026quot;, \u0026quot;appid\u0026quot;: \u0026quot;wx8888888888888\u0026quot;, ...}
channel.js文件,则是用来指定,当前需要编译哪个小程序,如:
module.exports = { channel: 'channel1'}
在默认的编译配置入口文件index.js中,我们需要配置小程序的输出目录,配置如下:
const channelInfo = require('./channel')const config = { ... //输入目录为dist_channel1 outputRoot: 'dist_' + channelInfo.channel, ... //讲config/channel1/project.config.json文件拷贝到dist_channel1下 copy: { patterns: [ { from: 'config/' + channelInfo.channel + '/project.config.json', to: 'dist_' + channelInfo.channel + '/project.config.json' } ], ... } ...}
执行Taro的小程序编译命令后,将会生成该分身对应的小程序代码文件夹dist_channel1,直接使用小程序开发者工具打开该目录,就可以进行channel1小程序的预览了。
通过这些配置,我们就可以通过同一套代码,生成多个不同的小程序啦!当然,这些小程序的内容是完全一样的,顶多就是project.config.json中配置的名字、appid有不同而已。
那么下面,我们就开始看看如何实现生成多个有差异化的小程序。
在具体实现之前,我们需要知道Taro两个重要的配置:
全局变量\u0026quot;defineConstants\u0026quot;;
别名\u0026quot;alias\u0026quot;。
影分身之样式分身
首先,我们来看看最常见的一种需求,那就是不同小程序之间,样式上的差别。我们先来看两张图。
小程序A:
小程序B:
在样式上,这两个小程序目前的区别有:
主色调不同
对应图片资源不同
排列样式不同
建立分身目录
第一步,在src下为每个分身小程序建立一个目录,名字最好与channel.js中的配置一样,如下图:
放置样式差异
以之前的“小程序A”来举例:
其中assets文件夹就是该小程序的资源文件,即各种蓝色的图标。
app.less为全局的样式文件,内容如下:
@main_color: #1296db;.main_color_txt { color: @main_color}
ChannelStyle.ts文件则为可能在代码中需要用到的样式:
const ChannelStyle = { mainColor: '#1296db'}export default ChannelStyle
配置别名
在放置好各类样式差异后,就可以进行全局变量和别名的配置了,在项目的config下的index.js中做如下配置:
const config = { ... alias: { '@/channel': path.resolve(__dirname, '..', 'src/channel/' + channelInfo.channel), '@/assets': path.resolve(__dirname, '..', 'src/channel/' + channelInfo.channel + '/assets'), '@/app_style': path.resolve(__dirname, '..', 'src/channel/' + channelInfo.channel + '/app.less'), } ...}
这样,在代码中就可以通过别名进行引用了:
//代码中需要用到ChannelStyle中的样式import ChannelStyle from '@/channel/ChannelStyle'//app.tsx入口文件引用全局样式import '@/app_style'//引用资源图片\u0026lt;Image src={require('@/assets/icon.png')} /\u0026gt;
另外请注意,由于目前Taro还未在.less等样式文件中支持别名,所以无法通过类似@import ‘@/app_style’的方式进行引用,所以目前需要在每个分身包下放置全量的差异样式。
配置全局变量
由于对于TabBar的配置,是纯字符串的形式,无法通过别名配置,所以需要使用另一种配置方式,也就是全局变量,在index.js的配置方式如下:
const config = { defineConstants: { ASSETS_PATH: 'channel/'+channelInfo.channel+'/assets' }}
但是主色调每个分身都不一样,所以需要在分身的配置文件中配置,就是基础配置中,分身文件夹下的config.js,在其中加入全局变量的配置:
module.exports = { ... defineConstants: { MAIN_COLOR: '#1296db' }, ...}
全局变量在代码中可以直接使用,如app.tsx中TabBar的配置:
config: Config = { ... tabBar: { ... selectedColor: MAIN_COLOR, list: [ { pagePath: 'pages/index/index', text: '首页', iconPath: ASSETS_PATH + '/home_u.png', selectedIconPath: ASSETS_PATH + '/home_s.png' }, ... ] } }
配置合并
在配置完成之后,在index.js文件最后的合并代码中,加上我们定义的分身配置:
module.exports = function (merge) { ... //默认的原始代码为return merge({}, config, envConfig) return merge({}, config, envConfig, require('./' + channelInfo.channel + \u0026quot;/config\u0026quot;))}
样式分身小结
如此,根据“小程序B”的资源文件和主题色配置之后,通过修改channel.js中的编译分身名,就可以生成这两个小程序了。
我们可能还发现,“小程序A”和“小程序B”的样式差异,除了资源图片和主题色之外,“开发”页面的布局方式也有差异,这该怎么处理呢?没错,还是通过别名指定less文件的方式,为各页面指定对应的样式文件。
如果说在实际业务中,不同的小程序存在明显的主题样式风格差异的话,建议可以建立主题包,然后为不同的小程序分身配置不同的主题包,如:
//分身配置module.exports = { ... alias: { '@/theme': path.resolve(__dirname, '..', '../src/theme/theme1'), ... } ...}//文件引用import '@/theme/dev.less'
影分身之功能分身
除了样式差异之外,有定制化属性的小程序一定也会存在一定的功能性差异。
细心的小伙伴可能发现了,“小程序A”和“小程序B”开发页面的条目数是不一样的。
“小程序A”并没有FireWall这一项,而且,这两个小程序的前两个条目Java和JSX的顺序是不一样的。不仅如此,如果运行小程序,点击各项的话你会发现,点击C++这一项,“小程序B”是跳转到条目详情页面,而“小程序A”则是跳转到“管理”Tab页。
类似这种功能性的差异,我们该如何处理呢?
定义页面配置
我所想到的思路是,给具有差异化的页面,提供差异化的配置项,然后通过合并的方式,合并具有差异的分身配置。
我们先来看定义完成后的配置目录,该目录在src下:
以“开发“页面为例,在DevConfig.ts中,我定义了如下的配置:
import Taro from \u0026quot;@tarojs/taro\u0026quot;;//页面配置export default { dev: { items: {//条目 item1: {//条目1 img: require('@/assets/jsx.png'),//图片 txt: 'JSX',//文字 onItemClick: () =\u0026gt; {//点击跳转事件 toPage('JSX', require('@/assets/jsx.png')) } }, item2: {...}, ... } }}//页面跳转function toPage(title, img){ Taro.navigateTo({url: '/pages/dev/DevInfo?title='+title+'\u0026amp;img='+img})}
定义差异合并
同时,diff包下的ChannelConfigDiff.ts文件,作为差异配置文件,其内容如下:
export default (config, merge)=\u0026gt;{ return merge([{}, config])}
可以看出,这其实就是把传入的config原封不动的返回了,因为对于项目主体来说,config是不需要改变的,具体的用途,会在下面说明。
而MultiChannelConfig.ts则为最终的各页面配置,内容如下:
import merge from 'deepmerge'import ChannelConfigDiff from '@/diff/ChannelConfigDiff'//开发页面配置import DevConfig from './pages/DevConfig'//合并基本页面配置const baseConfig = Object.assign({}, DevConfig)//合并差异页面配置const config = ChannelConfigDiff(baseConfig, merge.all)//开发页面最终配置export const devConfig = config.dev
定义差异配置
在上面的定义中,我们发现ChannelConfigDiff是根据别名引用的,现在大家应该明白ChannelConfigDiff.ts文件的作用了吧?没错,就是通过在各分身中加入这个文件,并编写配置。
以“小程序A”为例,diff目录如下:
在channel2的ChannelConfigDiff.ts中,只需要配置具体的差异项即可,未配置的则采用默认的配置:
const dev = { dev: { items: { item1: {//定义第一个item为java内容 img: require('@/assets/java.png'), txt: 'Java', onItemClick: () =\u0026gt; { toPage('Java', require('@/assets/java.png')) } }, item2: {...},//第二个item为jsx内容 item5: null,//第五个item(FireWall)为空 item8: { onItemClick: () =\u0026gt; {//最后一个item(C++)点击后跳转TAB Taro.switchTab({url: '/pages/index/Manage'}) } } } }}//将dev配置合并到原始整体配置export default (config, merge) =\u0026gt; { return merge([{}, config, dev])}
可以看到,该配置中,将item1(原jsx)和item2(原java)的内容对调,将item5(原FireWall)置空,将item8(原C++)点击事件改变。通过这些配置,以达到实现“小程序A”中的功能差异。
最后,别忘了别名的定义,在index.js中,别名配置为:
'@/diff': path.resolve(__dirname, '..', 'src/config/diff'),
在channel2的config.js中,别名配置为:
'@/diff': path.resolve(__dirname, '..', '../src/channel/channel2/diff'),
功能分身小结
如果有了其他的页面差异的话,通过类似的增加配置,来进行差异化处理,文件的目录格式并无要求,只需要保证配置文件名一致、别名配置正确就可以了。
这时,编译过后,生成的“小程序A”就拥有样式和功能差异化的“开发”页面了。
通过这种方式进行差异化配置,就要求对业务有较好的理解和对组件的合理拆分,并且定义出合理的配置项。
影分身之大差异分身
即便使用了样式分身和功能分身,依然可能出现一些巨大差异的定制化需求,这些巨大的差异导致样式分身和功能分身的配置成本过大,那这种情况下,该如何是好呢?
如果真的出现这种情况,那也只好断臂求生了 —— 那就是整体页面的替换。
我们来看看“小程序A”和“小程序B”的“管理页面”:
小程序A:
小程序B:
编写新页面
我们假设“小程序B”的“管理”页很难通过配置的方式去做差异化,那么这时,我们只有专门写一个新页面,目录如下:
其中pages下的就是专属于channel3的页面。
页面替换
替换页面的方式,其实也是通过全局变量。
index.js:
defineConstants: { PAGE_MANAGE: 'pages/index/Manage',}
channel3的config.js:
defineConstants: { PAGE_MANAGE: 'channel/channel3/pages/index/Manage'},
app.tsx的页面配置:
config: Config = { pages: [ ... PAGE_MANAGE ], ... tabBar: { ... list: [ ... { pagePath: PAGE_MANAGE, ... } ] } }
如此,编译后,channel3生成“小程序B”的“管理”页面,就是channel3独有的页面了。
总结
本文所提供的,只是我能够想到的一种解决“多个核心功能类似的小程序需要维护多套代码”这种窘境的方法,如果有更好的方法,希望各位能够告诉我,非常感谢。
由于本人只是一个刚接触前端不久的安卓开发,还有许多需要学习的地方,如果文中有误,欢迎指正批评。
具体的代码可以到Github查阅,也欢迎各位Star和提Issue。
最后,再次贴一下Github地址:https://github.com/BakerJQ/WeAppBunXin
更多内容,请关注前端之巅。
会议推荐
2019年6月,GMTC全球大前端技术大会2019即将到来。小程序、Flutter、移动AI、工程化、性能优化…大前端的下一站在哪里?点击下图了解更多详情。
相关文章:

TensorFlow全家桶的落地开花 | 2019 Google开发者日
作者 | 唐小引写于上海世博中心出品 | GDD 合作伙伴 CSDN(ID:CSDNnews)Android 10 原生支持 5G,Flutter 1.9、Dart 2.5 正式发布这是 Google Developer Days 在中国的第四年,从 2016 年 Google Developers 中国网站正式…

css的background
背景属性——background是css中的核心属性。你应该对它有充分的了解。这篇文章详细讨论了background的所有相关属性,甚至包括background-p_w_upload,还为我们介绍了它在即将到来的CSS3中的样子,还有那些新加入的背景属性。使用CSS2中的背景属…
windows7 64位机上配置支持GPU版(CUDA7.5)的OpenCV2.4.13操作步骤
很久之前在windows7 32位上配置过GPU版的opencv,可参考http://blog.csdn.net/fengbingchun/article/details/9831837Windows7 64位CUDA7.5的配置可以参考:http://blog.csdn.net/fengbingchun/article/details/53892997这里是在CUDA7.5已正确安装后的操作…

值得注意的知识点
ImageView的属性adjustViewBounds www.jianshu.com/p/13de17744… 转载于:https://juejin.im/post/5c8b7742e51d454e02716e44

阿里深度序列匹配模型SDM:如何刻画大型推荐系统的用户行为?
作者 | 石晓文来源 | 小小挖掘机(ID:wAIsjwj)今天给大家介绍的论文是:《SDM: Sequential Deep Matching Model for Online Large-scale Recommender System》论文下载地址:https://arxiv.org/abs/1909.00385v11、背景像…

find ip from hostname or find hostname from ip
1. find ip from hostname ping <hostname> 2.fin hostname from ip nslookup <ip>

Linux下多线程编程中信号量介绍及简单使用
在Linux中有两种方法用于处理线程同步:信号量和互斥量。线程的信号量是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行…

Linux环境HBase安装配置及使用
Linux环境HBase安装配置及使用 1. 认识HBase (1) HBase介绍 HBase Hadoop database,Hadoop数据库开源数据库官网:hbase.apache.org/HBase源于Google的BigTableApache HBase™是Hadoop数据库,是一个分布式,可扩展的大数据存储。当…

适合小团队作战,奖金+招聘绿色通道,这一届算法大赛关注下?
大赛背景伴随着5G、物联网与大数据形成的后互联网格局的逐步形成,日益多样化的用户触点、庞杂的行为数据和沉重的业务体量也给我们的数据资产管理带来了不容忽视的挑战。为了建立更加精准的数据挖掘形式和更加智能的机器学习算法,对不断生成的用户行为事…

Linq 集合处理(Union)
关于Union的两种情况 一、简单值类型或者string类型处理方式(集合需要实现IEnumerable接口) #region int类型List<int> ints1 new List<int> { 1, 2, 3, 4, 5, 6 };List<int> ints2 new List<int> { 5, 6, 7, 8, 9, 0 };IEnumerable<int> ints…

卷积神经网络中十大拍案叫绝的操作
作者 | Justin ho来源 | 知乎CNN从2012年的AlexNet发展至今,科学家们发明出各种各样的CNN模型,一个比一个深,一个比一个准确,一个比一个轻量。下面会对近几年一些具有变革性的工作进行简单盘点,从这些充满革新性的工作…
windows7下解决caffe check failed registry.count(type) == 1(0 vs. 1) unknown layer type问题
在Windows7下调用vs2013生成的Caffe静态库时经常会提示Check failed: registry.count(type) 1 (0 vs. 1) Unknown layer type的错误,如下图:这里参考网上资料汇总了几种解决方法:1. 不使用Caffe的静态库,直接将Caffe的sourc…

js 变量提升 和函数提升
2019独角兽企业重金招聘Python工程师标准>>> 创建函数有两种形式,一种是函数声明,另外一种是函数字面量,只有函数声明才有变量提升 console.log(a) // f a() { console.log(a) } console.log(b) //undefinedfunction a() {consol…

.net_ckeditor+ckfinder的图片上传配置
CKEditor和CKFinder的最新版可以到官方网站(http://cksource.com)上下载获得。 把以上两个资源放到网站的根目录: /CKEditor 和 /CKFinder (不区分大小写) 在页面使用 CKEditor: <textarea cols"80" id"prcont…
VS2013在Windows7 64位上变慢的解决方法
重装了windows7系统,又重装了vs2013,发现在打开vs2013、编译工程及调试的时候,vs2013都会变的比较慢,参考网上资料,这里列出几种可能的解决方法: 1. 打开工具--> 选项 --> 源代码管理 --> 插件选…

Key-Value数据库:Redis与Memcached之间如何选择?
华为云分布式缓存Redis5.0和Memcached都是华为云DCS的核心产品。 那么在不同的使用场景之下,如何选择Redis5.0和Memcached呢? 就由小编为大家进行详细的数据对比分析吧Redis和Memcached都是非常受欢迎的开源内存数据库,相对关系型数据库&…

裴健等9名华人当选加拿大皇家学会院士
【导读】近日,加拿大皇家学会(RSC,The Royal Society of Canada)官网宣布已评选出今年的新增院士。其中,京东副总裁、加拿大西蒙弗雷泽大学计算科学学院教授裴健和其他 8 名华人学者均在这份名单之中,而裴健…

Linux中shell命令的用法和技巧
使用Linux shell是我每天的基本工作,但我经常会忘记一些有用的shell命令和l技巧。当然,命令我能记住,但我不敢说能记得如何用它执行某个特定任务。于是,我开始在一个文本文件里记录这些用法,并放在我的Dropbox里&#…
Caffe中Layer注册机制
Caffe内部维护一个注册表用于查找特定Layer对应的工厂函数(Layer Factory的设计用到了设计模式里的工厂模式)。Caffe的Layer注册表是一组键值对(key, value)( LayerRegistry里用map数据结构维护一个CreatorRegistry list, 保存各个Layer的creator的函数句柄),key为L…

自动驾驶行业观察 | 停车不再难,L2到L4的泊车辅助系统技术剖析
作者 | 陈光来源 | 自动驾驶干货铺(ID:IntelligentDrive)【导读】在汽车智能化的浪潮中,车载传感器发展迅速,越来越多搭载了先进传感器的汽车进入了我们的视野。比如能够在高速公路上实现单车道巡航的凯迪拉克CT6,以及…

Unity Log重新定向
Unity Log重新定向 使用Unity的Log的时候有时候需要封装一下Debug.Log(message),可以屏蔽Log或者把log内容写到文本中。通过把文本内容传送到服务器中,查找bug出现的原因。但是封装之后的日志系统如果双击跳转的时候,会跳转到自定义的日志系统…

Javascript 检查一组 radio 中的哪一个被勾选
2019独角兽企业重金招聘Python工程师标准>>> 以前检查单选按钮是否被选择时,我使用的是 if else 一个一个的检查其 checked 属性。 这样虽然可以,但是当一组 radio 有很多个时,就很麻烦了。 可以通过 getElementsByName 得到所有…
二维码Aztec简介及其解码实现(zxing-cpp)
Aztec Code是1995年,由Hand HeldProducts公司的Dr. Andrew Longacre设计。它是一种高容量的二维条形码格式。它可以对ASCII和扩展ASCII码进行编码。当使用最高容量和25%的纠错级别的時候,Aztec可以对3000个字符或者3750个数字进行编码。Aztec的矩阵大小在…

顶配12699 元、没有5G,“浴霸三摄”的iPhone你会买吗?
作者 | 屠敏出品 | CSDN(ID:CSDNnews)北京时间 9 月 11 日凌晨 1 点,以「Apple 特别活动」为主题的苹果秋季发布会正式于史蒂夫乔布斯剧院拉开帷幕。按照惯例,在发布会之前,业界“毫不留情”地对新品进行了…

阿里P7架构师告诉你Java架构师必须知道的 6 大设计原则
在软件开发中,前人对软件系统的设计和开发总结了一些原则和模式, 不管用什么语言做开发,都将对我们系统设计和开发提供指导意义。本文主要将总结这些常见的原则,和具体阐述意义。 开发原则 面向对象的基本原则(solid)是五个&#…

rhel6用centos163 yum源
cd /etc/yum.repos.d/wget wget http://mirrors.163.com/.help/CentOS6-Base-163.reposed -i "s/\$releasever/6/" CentOS6-Base-163.repo

打破深度学习局限,强化学习、深度森林或是企业AI决策技术的“良药”
算法、算力和数据是人工智能时代的三驾马车,成为企业赋能人工智能的动力,但它们自身的特性也为企业和高校在研究和落地应用过程带来了重重挑战。比如,训练算法的成本高昂,数据从采集、处理到存储已面临瓶颈,目前针对算…

JAVA springboot微服务b2b2c电子商务系统(十三)断路器聚合监控(Hystrix Turbine)
讲述了如何利用Hystrix Dashboard去监控断路器的Hystrix command。当我们有很多个服务的时候,这就需要聚合所以服务的Hystrix Dashboard的数据了。这就需要用到Spring Cloud的另一个组件了,即Hystrix Turbine。一、Hystrix Turbine简介看单个的Hystrix D…
二维码Data Matrix的解码实现(zxing-cpp)
二维码Data Matrix的介绍可以参考http://blog.csdn.net/fengbingchun/article/details/44279967 ,以下是通过zxing-cpp开源库实现的对Data Matrix进行解码的测试代码:#include "funset.hpp" #include <string> #include <fstream> #include &…

PHP mongodb 的使用
mongodb 不用过多的介绍了,NOSQL的一种,是一个面向文档的数据库,以其方便灵活的数据结构,对于开发者来说是比较友好的,同时查询的速度也是比较快的,现在好多网站 开始使用mongodb ,具体的介绍可以网上查找。…