[巴别塔小程序]
话不多说,先看图简单说明一下自己做了什么。图片可能太模糊,可以点击svg查看。
背景
最近公司推出小程序业务,派我来负责这个业务。需要处理的问题之一是访问我们的web开发的传统体系结构——模块化开发。
让我们详细谈谈模块化开发是什么样的。
我们的git工作流程采用git流。一个项目分成几个模块,然后一个人负责一个模块的独立开发(一个对应git流的特性)。模块开发完成并与后端连接后,合并到Development中进行集成测试,然后经过一系列测试后发布版本。
结构大致如图所示。一个模块包含自己的页面/组件/资产/模型/混合/应用编程接口/路由/SCSS等等。
这种开发模式的优势不言而喻,每个人都可以并行开发,大大提高了开发速度。这一次,有必要将这种开发模式移植到小程序中。
目标
背景已经完成,那么我们来明确一下我们的目标。
我用的是wepy框架,开发的vue类语法,开发体验非常好。在vue中,组件是一个单独的文件,包含js、html和css。Wepy采用vue的语法,但与vue略有不同。wepy的组件分为三种类型——wepy . app类、wepy.page类和wepy.component类。
对应于我们的目录结构,每个模块实际上都是一系列的页面组件。要组合这一系列模块,非常简单。我们要做的就是把这一系列页面的路由扫描到一个路由表中,然后插入到applet - app.json的入口对应wepy框架,也就是app.wpy中的pages字段
export default类扩展了wepy . app { config={ pages 3360 ' modules/home/pages/index ',//这里!窗口: {背景文字样式:' light ',导航条barbackgroundcolor :' # fff ',导航条标题文字:' Hello,我是渣滓Hui ',导航条文字样式:' black'}}/.}
扫描路由表
Step 1先获取所有页面的路由,合成一个路由表!
我的计划是在每个模块中创建一个新的路由文件,相当于注册需要插入入口的每个页面的路由。不需要访问服务的页面不需要注册。你熟悉吗?可以,参考vue-router的注册语法就可以了。
//routes . js module . exports=[{ name : ' home-detail ',//todo3360name先占据一个位置,然后尝试通过读取名称跳转到页面:' detail '。//需要访问门户的页面的文件名。例如,这里是index.wpy相对于src/的路径是` modules/$ { modulename }/pages/index `。}、{name :' home-index '、page :' index '、meta : { weight 3360 100//这里增加了一个小函数,因为applet指定pages数组的第一项为主页,后面我会通过这个weight字段对路由的页面进行排序。重量越高,位置越靠前。}}}]扫描各个模块合并路由表的脚本很简单,读写文件就可以了。
const fs=require(' fs ')const path=require(' path ')const routed set=path . join(_ _ dirname,'./src/config/routes . js ')const Modulespath=path . join(_ _ dirname,'./src/modules’)让路由=[]fs . readdirsync(module path)。空军中尉
rEach(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...问题来了,如何替换入口文件中的路由数组。我如下做了几步尝试。
直接引入
我第一感觉就是,这不很简单吗?在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' } }//...}
然而这样小程序肯定会炸啦,pages字段的值必须是静态的,在小程序运行之前就配置好,动态引入是不行的!不信的话诸君可以试试。那么就是说,划重点---我们必须在wepy编译之前再预编译一次---事先替换掉pages字段的值!
正则匹配替换
既然要事先替换,那就是要精准定位pages字段的值,然后再替换掉。难点在于如果精准定位pages字段的值呢?
最捞然而最快的方法:正则匹配。
事先定好编码规范,在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__ */标记注释') } 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的变化如下:
//beforeexport default class extends wepy.app { config = { pages: [ /* __ROUTE__ */ /* __ROUTE__ */ ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: '大家好我是渣渣辉', navigationBarTextStyle: 'black' } }//...}//afterexport default class extends wepy.app { config = { pages: [/* __ROUTE__ */'modules/home/pages/index', /* __ROUTE__ */ ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: '大家好我是渣渣辉', navigationBarTextStyle: 'black' } }//...}
行吧,也总算跑通了。因为项目很赶,所以先用这个方案开发了一个半星期。开发完之后总觉得这种方案太难受,于是密谋着换另一种各精准的自动的方案。。。