小程序開發之影分身術

前言

影分身術,看過火影的都知道,一個本體,多個分身。html

你們確定要問了,那小程序開發跟影分身術也能扯上關係?沒錯,那天然就是:一套代碼,多個小程序啦。前端

各位先別翻白眼,且聽我細細說來。。。java

現在小程序發展如日中天,再加上微信的力推,不少公司的業務也都慢慢的轉向小程序,這讓我這個安卓開發,也不得不開始了小程序開發之旅。git

然而隨着公司的發展,客戶愈來愈多,核心功能相同的小程序,須要上架多個小程序分別給不一樣的客戶使用,每一個小程序之間又存在這一小部分的定製化,好比界面展現的不一樣、小功能的差別等等。github

這可以讓我這個剛接觸小程序開發的前端菜鳥抓狂了,每一個小程序複製一份代碼出來,而後作定製化的修改?這豈不是若是哪天核心業務有改動,我得對每套代碼分別改動一次?不行,即便是菜鳥,對這種弄出多套重複代碼的行爲也是沒法容忍的!!json

因而,就有了針對這種場景下的一個解決方案:給小程序開發來個影分身術。小程序

Github地址:https://github.com/BakerJQ/We...微信

該項目基於Taro框架,由凹凸實驗室開源,很是感謝他們的努力付出。app

之因此選用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/We...

相關文章
相關標籤/搜索