前言
影分身術,看過火影的都知道,一個本體,多個分身。
大家肯定要問了,那小程式開發跟影分身術也能扯上關係?沒錯,那自然就是:一套程式碼,多個小程式啦。
各位先別翻白眼,且聽我細細說來。。。
如今小程式發展如日中天,再加上微信的力推,很多公司的業務也都慢慢的轉向小程式,這讓我這個安卓開發,也不得不開始了小程式開發之旅。
然而隨著公司的發展,客戶越來越多,核心功能相同的小程式,需要上架多個小程式分別給不同的客戶使用,每個小程式之間又存在這一小部分的定製化,比如介面展示的不同、小功能的差異等等。
這可讓我這個剛接觸小程式開發的前端菜鳥抓狂了,每個小程式複製一份程式碼出來,然後做定製化的修改?這豈不是如果哪天核心業務有改動,我得對每套程式碼分別改動一次?不行,即使是菜鳥,對這種弄出多套重複程式碼的行為也是無法容忍的!!
於是,就有了針對這種場景下的一個解決方案:給小程式開發來個影分身術。
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兩個重要的配置:全域性變數"defineConstants"和別名"alias"。
影分身之樣式分身
首先,我們來看看最常見的一種需求,那就是不同小程式之間,樣式上的差別。我們先來看兩張圖。
小程式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