写点什么

多重影分身:一套代码如何生成多个小程序?

  • 2019-03-14
  • 本文字数:5333 字

    阅读完需:约 17 分钟

多重影分身:一套代码如何生成多个小程序?

前言

影分身术,看过火影的都知道,一个本体,多个分身。


大家肯定要问了,那小程序开发跟影分身术也能扯上关系?没错,那自然就是:一套代码,多个小程序啦。


各位先别翻白眼,且听我细细说来。


如今小程序发展如日中天,再加上微信的力推,很多公司的业务也都慢慢的转向小程序,这让我这个安卓开发,也不得不开始了小程序开发之旅。


然而随着公司的发展,客户越来越多,核心功能相同的小程序,需要上架多个小程序分别给不同的客户使用,每个小程序之间又存在这一小部分的定制化,比如界面展示的不同、小功能的差异等等。


这可让我这个刚接触小程序开发的前端菜鸟抓狂了,每个小程序复制一份代码出来,然后做定制化的修改?这岂不是如果哪天核心业务有改动,我得对每套代码分别改动一次?不行,即使是菜鸟,对这种弄出多套重复代码的行为也是无法容忍的!


于是,就有了针对这种场景下的一个解决方案:给小程序开发来个影分身术。


Github 地址:https://github.com/BakerJQ/WeAppBunXin


该项目基于 Taro 框架,由凹凸实验室开源,非常感谢他们的努力付出。


之所以选用 Taro,主要是因为它采用 React 语法标准,而本人之前有过 ReactNative 开发经验。


由于本人接触前端开发时间不长,文中若出现了错误或者有更好的方案,欢迎各位包容和指正,万分感谢。

影分身之基础配置

影分身的能力,主要来源于 Taro 所提供的编译能力,所以需要对 Taro 的编译配置和编译配置详情有所了解。


我们先来看看配置的相关文件目录:



config 目录为 Taro 初始化后的默认配置目录,图中蓝色框框内的三个文件(dev、index、prod)为默认生成的配置文件,剩下的文件,则为分身所需的配置。图中配置了三个分身,我们以 channel1 为例,config 是该分身的一些配置,project.config.json 就是该分身小程序的基本配置,如:


{    "miniprogramRoot": "./",    "projectname": "channel1",    "description": "channel1",    "appid": "wx8888888888888",    ...}
复制代码


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 两个重要的配置:


影分身之样式分身

首先,我们来看看最常见的一种需求,那就是不同小程序之间,样式上的差别。我们先来看两张图。


小程序 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'//引用资源图片<Image src={require('@/assets/icon.png')} />
复制代码


另外请注意,由于目前 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 + "/config"))}
复制代码

样式分身小结

如此,根据“小程序 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 "@tarojs/taro";//页面配置export default {  dev: {    items: {//条目      item1: {//条目1        img: require('@/assets/jsx.png'),//图片        txt: 'JSX',//文字        onItemClick: () => {//点击跳转事件          toPage('JSX', require('@/assets/jsx.png'))        }      },      item2: {...},      ...    }  }}//页面跳转function toPage(title, img){  Taro.navigateTo({url: '/pages/dev/DevInfo?title='+title+'&img='+img})}
复制代码

定义差异合并

同时,diff 包下的 ChannelConfigDiff.ts 文件,作为差异配置文件,其内容如下:


export default (config, merge)=>{  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: () => {          toPage('Java', require('@/assets/java.png'))        }      },      item2: {...},//第二个item为jsx内容      item5: null,//第五个item(FireWall)为空      item8: {        onItemClick: () => {//最后一个item(C++)点击后跳转TAB          Taro.switchTab({url: '/pages/index/Manage'})        }      }    }  }}//将dev配置合并到原始整体配置export default (config, merge) => {  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、工程化、性能优化…大前端的下一站在哪里?点击下图了解更多详情。



2019-03-14 10:425317

评论

发布
暂无评论
发现更多内容

又是供应链安全:GUAC项目0.1版释出

sender_is_sender

网络安全 开源软件 供应链安全 GUAC

Photoshop2023beta常见问题|ps 2023测试版智能AI功能不能用如何解决?

Rose

Photoshop2023beta FireflyAI

Waves 14 Complete(Waves混音效果全套插件)安装激活教程

Rose

Waves 14破解教程 Waves 下载 Waves 14 最新版 Waves混音效果全套插件

在软件定义汽车的时代,低代码究竟给车企数字化转型带来了什么?

优秀

低代码 车企 车企数字化

智能出行 驱动未来|2023开放原子全球开源峰会CARSMOS开源智能出行生态年会即将启幕

开放原子开源基金会

开源 智能出行 CARSMOS

Android File Transfer for mac(强大的安卓文件传输工具)

Rose

安卓文件传输工具 Android File Transfer Android和mac互通

Waves 14 Complete Mac激活最新版(Waves混音效果全套插件) v31.05.2023

真大的脸盆

Mac 软件 Waves 14破解教程 混音软件 混音套件 waves

abaqus有限元仿真常见问题(一):数值仿真在产品研发中的作用

思茂信息

CAE abaqus abaqus软件 abaqus有限元仿真

提升您的 MQTT 云服务:深入探索 BYOC

EMQ映云科技

物联网 云服务 mqtt

Scrum敏捷项目管理真实案例

顿顿顿

Scrum 敏捷开发 敏捷开发管理 敏捷项目管理 scrum工具

鲲鹏DevKit助力南京信息工程大学高效开发

说山水

鲲鹏DevKit助力深圳财富趋势高效开发行情分析系统,行情揭示快人一步

说山水

植物大战僵尸mac版下载

Rose

植物大战僵尸 mac 植物大战僵尸中文版 植物大战僵尸下载 mac塔防游戏

mac电脑如何远程控制win?微软远程桌面连接工具来帮你!

Rose

Mac远程控制软件 mac电脑如何远程控制win microsoft remote desktop 微软远程桌面连接工具

led显示屏保养技巧

Dylan

故障 LED显示屏 设备日常保养

AI实战营第二期_OpenMMLab概述

IT蜗壳-Tango

OpenMMLab

Qcon 广州主题演讲:融云实时社区的海量消息分发实践

融云 RongCloud

通信 IM 社交 融云 Discord

支持M1的苹果电脑视频下载器-Downie 4 Mac中文版

Rose

Mac 视频下载工具 Downie 4许可证 Downie 4中文版 Downie mac破解版

挖掘算力产业的创新力量和新型机遇|2023开放原子全球开源峰会先进计算分论坛即将启幕

开放原子开源基金会

开源 算力 先进计算

华为教育中心:奇趣“童”行庆六一 多彩儿童应用助成长

科技热闻

开源创新 协同融合|2023开放原子全球开源峰会开源协作平台分论坛即将启幕

开放原子开源基金会

开源 开源协作

ps 2023 v24.6beta激活版下载 支持AI功能

Rose

Photoshop 2023下载 PS测试版下载 FireflyAI

对应用数据开发还有疑惑?看这篇就够了!数据存储、管理,通通掌握!

HarmonyOS开发者

HarmonyOS

FC红白机游戏600合集 for mac(小霸王游戏) 单机离线版

Rose

Mac游戏下载 FC 红白机游戏 任天堂游戏下载 小霸王游戏

乘云而上,山西软件行业CXO思享会在晋起航

说山水

如何在大学校园内合作运营共享电单车

共享电单车厂家

校内共享电单车 共享电动车投放 共享电单车合作 共享电单车厂家

AI2023下载 Illustrator mac版 2023 年 4 月版(版本 27.5)的新增功能

理理

AI2023下载 Adobe Illustrator2023 AI2023完美解锁版

Mac电脑ps ai beta安装教程及ai绘图如何使用

理理

ps AI绘图 ps ai beta Firefly AI使用教程

Java 高效处理千万级数据:技巧与最佳实践

xfgg

Java 大数据

2023 重新开始

HoneyMoose

解锁软件工程新角色——平台工程师

SEAL安全

DevOps 平台工程 平台工程师

多重影分身:一套代码如何生成多个小程序?_大前端_BakerJ_InfoQ精选文章