話不多說先上圖,簡要說明一下干了些什么事。圖可能太模糊,可以點(diǎn) svg 看看
最近公司開展了小程序的業(yè)務(wù),派我去負(fù)責(zé)這一塊的業(yè)務(wù),其中需要處理的一個(gè)問題是接入我們web開發(fā)的傳統(tǒng)架構(gòu)-- 模塊化開發(fā) 。
我們來詳細(xì)說一下模塊化開發(fā)具體是怎么樣的。
目錄結(jié)構(gòu)大體如圖所示,一個(gè)模塊包含了他自己的pages / components / assets / model / mixins / apis / routes / scss等等。
這種開發(fā)模式的好處不言而喻,每個(gè)人都可以并行開發(fā),大大提升開發(fā)速度。這次就是要移植這種開發(fā)模式到小程序中。
背景說完了,那么來明確一下我們的目標(biāo)。
對應(yīng)到我們的目錄結(jié)構(gòu)中,每個(gè)模塊實(shí)際上就是一系列的page組件。要組合這一系列的模塊,那么很簡單,我們要做的就是把這一系列page的路由掃描成一個(gè)路由表,然后 插入到小程序的入口--app.json中 。對應(yīng)wepy框架那即是app.wpy中的pages字段。
export default class extends wepy.app {
config = {
pages: 'modules/home/pages/index',//here!!!!
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: '大家好我是渣渣輝',
navigationBarTextStyle: 'black'
}
}
//...
}
第一步!先得到所有pages的路由并綜合成一個(gè) 路由表 !
我的方案是,在每個(gè)模塊中新建一份routes文件,相當(dāng)于注冊每個(gè)需要插入到入口的page的路由,不需要接入業(yè)務(wù)的page就不用注冊啦。是不是很熟悉呢,對的,就是參考vue-router的注冊語法。
//routes.js
module.exports = [
{
name: 'home-detail',//TODO: name先占位,后續(xù)再嘗試通過讀name跳轉(zhuǎn)某頁
page: 'detail',//需要接入入口的page的文件名。例如這里是index.wpy。相對于src/的路徑就是`modules/${moduleName}/pages/index`。
},
{
name: 'home-index',
page: 'index',
meta: {
weight: 100//這里加了一個(gè)小功能,因?yàn)樾〕绦蛑付╬ages數(shù)組的第一項(xiàng)為首頁,后續(xù)我會(huì)通過這個(gè)權(quán)重字段來給pages路由排序。權(quán)重越高位置越前。
}
}
]
而掃描各個(gè)模塊并合并路由表的腳本非常簡單,讀寫文件就ok了。
const fs = require('fs')
const path = require('path')
const routeDest = path.join(__dirname, '../src/config/routes.js')
const modulesPath = path.join(__dirname, '../src/modules')
let routes = []
fs.readdirSync(modulesPath).forEach(module => {
if(module.indexOf('.DS_Store') > -1) return
const route = require(`${modulesPath}/${module}/route`)
route.forEach(item => {
item.page = `modules/${module}/pages/${item.page.match(/\/?(.*)/)[1]}`
})
routes = routes.concat(route)
})
fs.writeFileSync(routeDest,`module.exports = ${JSON.stringify(routes)}`, e => {
console.log(e)
})
路由排序策略
const strategies = {
sortByWeight(routes) {
routes.sort((a, b) => {
a.meta = a.meta || {}
b.meta = b.meta || {}
const weightA = a.meta.weight || 0
const weightB = b.meta.weight || 0
return weightB - weightA
})
return routes
}
}
最后得出路由表
const Strategies = require('../src/lib/routes-model')
const routes = Strategies.sortByWeight(require('../src/config/routes'))
const pages = routes.map(item => item.page)
console.log(pages)//['modules/home/pages/index', 'modules/home/pages/detail']
So far so good...問題來了,如何替換入口文件中的路由數(shù)組。我如下做了幾步嘗試。
我第一感覺就是,這不很簡單嗎?在wepy編譯之前,先跑腳本得出路由表,再import這份路由表就得了。
import routes from './routes'
export default class extends wepy.app {
config = {
pages: routes,//['modules/home/pages/index']
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: '大家好我是渣渣輝',
navigationBarTextStyle: 'black'
}
}
//...
}
然而這樣小程序肯定會(huì)炸啦,pages字段的值必須是靜態(tài)的,在小程序運(yùn)行之前就配置好,動(dòng)態(tài)引入是不行的!不信的話諸君可以試試。那么就是說,劃重點(diǎn)--- 我們必須在wepy編譯之前再預(yù)編譯一次 ---事先替換掉pages字段的值!
既然要事先替換,那就是要精準(zhǔn)定位pages字段的值,然后再替換掉。難點(diǎn)在于如果精準(zhǔn)定位pages字段的值呢?
最撈然而最快的方法:正則匹配。
事先定好編碼規(guī)范,在pages字段的值的前后添加 /* __ROUTES__ */ 的注釋
腳本如下:
const fs = require('fs')
const path = require('path')
import routes from './routes'
function replace(source, arr) {
const matchResult = source.match(/\/\* __ROUTE__ \*\/([\s\S]*)\/\* __ROUTE__ \*\//)
if(!matchResult) {
throw new Error('必須包含/* __ROUTE__ */標(biāo)記注釋')
}
const str = arr.reduce((pre, next, index, curArr) => {
return pre += `'${curArr[index]}', `
}, '')
return source.replace(matchResult[1], str)
}
const entryFile = path.join(__dirname, '../src/app.wpy')
let entry = fs.readFileSync(entryFile, {encoding: 'UTF-8'})
entry = replace(entry, routes)
fs.writeFileSync(entryFile, entry)
app.wpy的變化如下:
//before
export default class extends wepy.app {
config = {
pages: [
/* __ROUTE__ */
/* __ROUTE__ */
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: '大家好我是渣渣輝',
navigationBarTextStyle: 'black'
}
}
//...
}
//after
export default class extends wepy.app {
config = {
pages: [
/* __ROUTE__ */'modules/home/pages/index', /* __ROUTE__ */
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: '大家好我是渣渣輝',
navigationBarTextStyle: 'black'
}
}
//...
}
行吧,也總算跑通了。因?yàn)轫?xiàng)目很趕,所以先用這個(gè)方案開發(fā)了一個(gè)半星期。開發(fā)完之后總覺得這種方案太難受,于是密謀著換另一種各精準(zhǔn)的自動(dòng)的方案。。。