uniapp实现模块化路由管理并监听模块文件变化自动生成pages.json
需求背景:
在项目组使用uniapp开发移动端app时,由于是多人运动(八个人),大家都要去修改pages.json文件,导致经常冲突,随着pages.json记录信息越来越多,总会导致不知道怎么回事谁的页面就跳不过去或者报错,我这暴脾气就忍不了了,于是想研究一下能不能把路由都拆出来进行模块化管理,这样做的好处就在于麻麻再也不用担心我们解决代码冲突时不小心删除别人的代码而打架啦
需求价值:
模块化的好处不言而喻,解耦之后大家都可以只关注自己的模块文件,不必提心吊胆生怕删掉别人的代码。
模块化以后使得我们的维护工作更加便捷高效,隐性的对工作效率也是一种提升。(试想一下几百个页面路由在同一个文件中八个人维护...我是谁?我在哪?我在干嘛?)
其他的好处可以自行评估,在这里我自己是为了这两点才下定决心搞它的。
准备工作:
① 思考用什么方式将pages.json中的代码拆分成不同模块进行管理
② 思考怎么将所有的模块文件整合生成json
③ 熟悉fs模块文档
开始进行:
1、路由分模块管理
在项目中的router文件夹下创建modules文件夹进行管理所有的模块,每个模块我们用熟悉的js进行管理,如下图所示
代码示例 :其他模块文件结构相同。
module.exports = { baseUrl: 'pages/A/', children: [ { path: "test", style: { navigationStyle: "custom", enablePullDownRefresh: false } }, ] }
2、将pages.json中除pages以外的其他配置参数拆分(uniConfig.js),放置router文件夹下与modules同级
uniConfig.js代码示例:
module.exports = { globalStyle: { navigationBarTextStyle: "black", navigationBarTitleText: "项目名称", navigationBarBackgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF" }, tabBar: { color: "#000000", selectedColor: "#1B7AFF", backgroundColor: "#FFFFFF", fontSize: "11px", height: "54px", // #ifdef APP-PLUS borderStyle: "#EBEDF1", // #endif list: [{ pagePath: "pages/home/index", text: "首页", iconPath: "static/img/tabbar/icon_tabbar_home_n.png", selectedIconPath: "static/img/tabbar/icon_tabbar_home_s.png" }, { pagePath: "pages/mine/index", text: "我的", iconPath: "static/img/tabbar/icon_tabbar_mine_n.png", selectedIconPath: "static/img/tabbar/icon_tabbar_mine_s.png" }, { pagePath: "pages/test/test", text: "test", iconPath: "static/img/tabbar/icon_tabbar_home_n.png", selectedIconPath: "static/img/tabbar/icon_tabbar_home_s.png" } ] }, 'app-plus': { renderer: "native", softinputMode: "adjustPan" } }
3、在router文件夹下新建build.js,用于整合所有模块并生成pages.json,读取文件跟写入文件用到了node的fs模块
build.js代码示例:
const fs = require('fs') const path = require('path') const router = require('./uniConfig.js') // 将子路由模块配置文件转化为 uniapp 配置文件格式 const buildRouter = route => { const res = [] const { baseUrl, children } = route children.forEach(i => { const obj = { ...i, path: baseUrl + i.path, } res.push(obj) }) return res } // 加载 './modules' 目录子路由配置文件 const getRouter = () => { const srcPath = path.resolve(__dirname, './modules') const result = fs.readdirSync(srcPath) let router = [] result.forEach(r => { const route = require('./modules/' + r) router = [...router, ...buildRouter(route)] }) return router } // 构建 pages 并写入 pages.json 文件 router.pages = getRouter() console.log(router) fs.writeFile( __dirname + '/../pages.json', // 用两个空格来缩进 pages.json,如果喜欢制表符,第三个参数更换为 \t 即可 JSON.stringify(router, null, ' '), e => e ? console.error(e) : console.log('pages.json 配置文件更新成功') )
4、在项目package.json中添加命令后,运行npm run build-router命令即可重新生成pages.json(或者在项目根目录下运行node router/build.js)
以上代码思路参考文章:https://blog.csdn.net/fungleo/article/details/105165774
---------------------------------------------------------------------------华丽的分割线-----------------------------------------------------------------------------------
上面的方式用起来以后真的香,但马上喜极而泣又有了新问题,HbuilderX对uniapp中的pages.json做了特殊处理,支持条件编译(#ifdef)和注释,采用上面的方式引入模块对象时无法得到注释内容,写入json文件时也无法正确输出条件编译(#ifdef)跟注释,因为不确定随着项目扩展会在哪些地方加上条件编译跟注释,去掉这个特性也不太好,如果不改动原有的模块配置,引入js对象时注释就没了,如果用fs.readFile读文件,又要写一大堆的正则去替换实在太麻烦(一开始没思路的时候我试过用将模块文件拆分成不同json的方式、用fs.readFile读js转string的方式、用txt,结果是都失败了,踩过的坑这里提醒一下),因此有了以下的变化:
1、改写uniConfig.js
注意:
① 注意字符串模板中按照标准json格式去写
② 每一个配置文件的最后一项不要加逗号,否则不好拼接字符串
uniConfig.js代码如下:
module.exports = { data: ` "globalStyle": { "navigationBarTextStyle": "black", "navigationBarTitleText": "绿瘦小易", "navigationBarBackgroundColor": "#FFFFFF", "backgroundColor": "#FFFFFF" }, "tabBar": { "color": "#000000", "selectedColor": "#1B7AFF", "backgroundColor": "#FFFFFF", "fontSize": "11px", "height": "54px", // #ifdef APP-PLUS "borderStyle": "#EBEDF1", // #endif "list": [ { "pagePath": "pages/home/index", "text": "首页", "iconPath": "static/img/tabbar/icon_tabbar_home_n.png", "selectedIconPath": "static/img/tabbar/icon_tabbar_home_s.png" }, { "pagePath": "pages/customer/customList/customList", "text": "客户", "iconPath": "static/img/tabbar/icon_tabbar_client_n.png", "selectedIconPath": "static/img/tabbar/icon_tabbar_client_s.png" }, { "pagePath": "pages/im/index", "text": "IM", "iconPath": "static/img/tabbar/icon_tabbar_im_n.png", "selectedIconPath": "static/img/tabbar/icon_tabbar_im_s.png" }, { "pagePath": "pages/mine/index", "text": "我的", "iconPath": "static/img/tabbar/icon_tabbar_mine_n.png", "selectedIconPath": "static/img/tabbar/icon_tabbar_mine_s.png" }, { "pagePath": "pages/test/test", "text": "test", "iconPath": "static/img/tabbar/icon_tabbar_home_n.png", "selectedIconPath": "static/img/tabbar/icon_tabbar_home_s.png" } ] }, "app-plus": { "renderer": "native", "softinputMode": "adjustPan" }, "easycom": { "autoscan": true, "custom": { "^ls-(.*)": "@/components/ls-$1/ls-$1.vue" } } // 最后一项这里不要添加逗号 ` }
2、使用字符串模板改写现有的modules下的文件。
代码示例:其他模块结构配置相同
let baseUrl = 'pages/A/'; module.exports = { data: ` { "path": "${baseUrl}test", "style": { "navigationStyle": "custom", "app-plus": { "bounce": "vertical", "scrollIndicator": "none" } } } // 最后一项这里不要添加逗号 ` }
3、改写build.js,代码示例:
const fs = require('fs') const path = require('path') // 引入除pages以外的其他uniapp属性配置 const baseData = require('./uniConfig.js') // 获取modules目录 const srcPath = path.resolve(__dirname, './modules') const pages = []; // 路由模块 // 读取modules目录下的所有路由模块js fs.readdir(srcPath, function(err, files) { if(err) throw err; // 将所有的路由模块全部存入数组 files.forEach(function(file) { var modulesFile = require(__dirname + '/modules/' + file) pages.push(modulesFile.data); }) // 将数据拼接成pages.json支持的格式 let pageJson = "{" + baseData.data + ",\n" + "\"pages\": [" + pages.join(',') + "]" + "}"; console.log(pageJson) fs.writeFile( __dirname + '/../pages.json', pageJson, e => e ? console.error(e) : console.log('pages.json 配置文件更新成功') ) } )
由于每次修改文件后都要手动执行npm run build-router命令略显麻烦,为了更好的提高工作效率(没错,就是为了偷懒),我搜索引擎了一下能监听文件改变的node包,supervisor、nodemon的使用人数好像比较多,但文档实在有点...难以言喻,这里我使用了chokidar,在监听到文件改动后使用node子进程运行命令自动生成代码。
4、在项目中运行 npm install chokidar -D 安装好chokidar,然后在router文件夹下新建chokidar.js,代码示例:
const chokidar = require('chokidar'); // 引入node子进程,node环境自带,无需额外安装 const exec = require('child_process').exec; const cmd = 'node router/build.js' // 监听uniConfig.js文件改变并执行node命令 chokidar.watch(__dirname + '/uniConfig.js').on('change', (event, path) => { console.log(event, path); // 监听到文件改变后执行命令输出pages.json exec(cmd, function(error, stdout, stderr) { // 获取命令执行的输出 }); }) // 监听modules子文件改变并执行node命令 chokidar.watch(__dirname + '/modules').on('all', (event, path) => { console.log(event, path); // 监听到文件改变后执行命令输出pages.json exec(cmd, function(error, stdout, stderr) { // 获取命令执行的输出 }); })
chokidar文档: https://www.npmjs.com/package/chokidar
5、在package.json中配置watch-router命令:
以上配置完成后,在项目中运行npm run watch-router命令即可自动监听文件改动生成pages.json,也可以手动运行npm run build-router生成文件。