uni-app路由模块化脚本解决方案

365 阅读5分钟

前言

之前因为uni-app所有的页面都是放在pages.json文件中的,但是json文件中是没法写注释的,所以我们没法较好的去按模块分路由。后来用了插件去进行处理模块化,但是当我们使用vue3时,将vite升级到vite4版本时,就没法支持那个插件了。那个解决方案在俺之前的文章也有介绍。加上之前的插件有些小bug,早就想给优化下,所以就搞了个脚本来动态的获取模块路由,当路由文件有所变化,就会触发脚本函数去收集路由,完成路由收集后对pages.json进行重写,达到拆分路由并且动态重写路由的效果。

准备工作

实现过程其实是有点小曲折的,核心就是监听路由文件,监听文件俺这边选用了chokidar文件观察库来监听路由文件,然后就是通过node的fs模块功能重写pages.json文件,还有个小瑕疵就是监听文件服务要和项目要一起启动。其实可以多开一个控制台,但是我觉得这样用起来效果不好,所以合在一个命令中比较好。所以俺又使用了concurrently来同时启用两个服务。

// 安装依赖
pnpm install chokidar -D
pnpm install concurrently -D

chokidar

chokidar源码的git地址
chokidar就是文件观察库,它能够帮助你去监听指定目录下的文件变更。我们监听路由文件变化就能使用它。

watch函数

chokidarwatch数接收的第一个参数是路径,第二个参数是一些选项,但是这次没用上第二个参数。它的事件有add、on、unwatch等,但是俺这边只用了on事件, on事件的回调有两个参数,分别是更改文件的方式,以及文件路径。这个正好告诉我们哪个文件进行了修改,并且告诉我们是新增还是删除还是修改。

重写pages.json

重写pages.json要用到的是node的fs模块写入文件功能,重写的时候要注意,经过俺的多次尝试,需要把登录页放在路由的第一项,因为重写后,页面缓存就没了,如果你当前的页面在深的层级中,重写后就没法返回了。

concurrently

concurrently这个插件允许你一次启动多个服务,我们启动项目时不仅需要启动项目,也需要启动监听文件服务,我们也可以开两个控制台启动,但是太麻烦,所以通过concurrently这个插件来帮助我们在一个控制台中一次启动两个服务。

"scripts": {
              "serve": "uni",
              "serve:cs": "concurrently \"uni --port 3001 --mode cs\" \"node script/index.js\""
        }

完整代码

目录结构

path.png

index.js

console.log('运行监听路由脚本成功');
const chokidar = require('chokidar');
const fs = require('fs');
const path = require('path');

// 记录修改文件的次数
let count = 0;

// 路由文件路径、监听该路径下所有路由文件
let routerPath = path.join(__dirname, '../src/page_config/router');

// 需要重写pages.json的路径
let writePath = path.join(__dirname, '../src/pages.json');

/**
 * @description 读取router下的所有路由文件
 * @param {string} url router路径
 * @return urlObj 路由文件路径集合
 */
const readDir = (url) => {
    const dirInfo = fs.readdirSync(url);
    let urlObj = {};
    dirInfo.map((item) => {
        const location = path.join(url, item);
        console.log('路由文件路径=> ' + location);
        urlObj[location] = location;
    });
    return urlObj;
};

// 路由文件路径集合
const originObj = readDir(routerPath);

/**
 * @description 监听所有路由文件
 */
chokidar.watch(routerPath).on('all', (event, path) => {
    // 移除路由文件、更新pages.json
    if (event === 'unlink') {
        delete originObj[path];
        updateRouter(routerPath);
        return;
    }

    // 新增路由文件,并未改动,不更新
    // 实际上要是再细点需要去判断这个新增的路由文件是否是通过复制整个文件粘贴进来的,不是通过右击新增的空文件夹。复制的它有可能是一个完整的目录,需要去更新pages.json的。
    if (event === 'add') {
        originObj[path] = path;
        return;
    }

    // 路由文件保存,更新pages.json
    if (event === 'change') {
        updateRouter(routerPath);
    }
});

/**
 * @description 重写pages.json
 * @param {string} url router路径
 * @return void
 */
const updateRouter = (url) => {
    const dirInfo = fs.readdirSync(url);
    // 存放所有路由
    let urlList = [];
    dirInfo.map((item) => {
        const location = path.join(url, item);
        // node会缓存模块,需要清除缓存,以获取最新的文件内容
        delete require.cache[require.resolve(location)];

        if (originObj[location]) {
            urlList.push(...require(originObj[location]));
        }
    });
    // router.js存放一些默认配置,也可优化成动态拼接不同的模块
    const initConf = require('./router.js');
    // 需要将登录页放在所有路由的第一项,因为重写pages.json后会丢失uni-app的页面缓存
    // 用于存放合并路由后的登录页所对应的数组下标
    let deleteIndex = 0;
    // 用于存放登录路由
    let copyItem = {};

    urlList.forEach((item, index) => {
        if (item.style.navigationBarTitleText === '登录') {
            copyItem = item;
            deleteIndex = index;
        }
    });
    // 去除路由集合中不知道处于第几项的登录路由
    urlList.splice(deleteIndex, 1);
    // 将登录路由放到路由集合的第一项
    urlList.unshift(copyItem);

    // 更新pages.json的配置
    initConf.pages = urlList;
    count++;
    // 重写src目录下的pages.json文件
    fs.writeFile(writePath, JSON.stringify(initConf, null, '  '), (e) =>
        e ? console.error(e) : console.log(`路由修改成功,如果页面报错,请重新点击保存,修改次数为${count}`)
    );
};

router.js

    module.exports = {
        easycom: {
            autoscan: true,
            custom: {
                '^u-(.*)': 'uview-plus/components/u-$1/u-$1.vue',
                '^uni-(.*)': '@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue',
            },
        },
        pages: [],
        tabBar: {
            color: '#7A7E83',
            selectedColor: '#2196f3',
            backgroundColor: '#fff',
            height: '50px',
            list: [
                {
                    pagePath: 'pages/index/index',
                    iconPath: 'static/product.png',
                    text: '生产',
                    selectedIconPath: 'static/product-active.png',
                },
                {
                    pagePath: 'pages/setting/index',
                    iconPath: 'static/setting.png',
                    text: '设置',
                    selectedIconPath: 'static/setting-active.png',
                },
            ],
        },
        globalStyle: {
            navigationBarTextStyle: 'black',
            navigationBarTitleText: 'uni-app',
            navigationBarBackgroundColor: '#F8F8F8',
            backgroundColor: '#F8F8F8',
        },
    };

优化

在我看来还是有一些需要优化的点,vite热更新也是通过chokidar来监听文件变化的,实际上如果可以通过vite的热更新回调来对路由进行监听修改的话,是可以省去引入chokidarconcurrently这一步的。尝试过使用vite官网中的import.meta.hotAPI来拦截文件变动并进行路由的重新加载,但是使用过程中出现了拿不到回调参数的问题,copy官网的在项目中也不生效,可能是项目构建时一些插件相互影响了,也可能是俺使用的有问题,也可能是bug。

总结

总得来说,实现的过程还是踩了一些坑的,但是目前已经用在项目上了,完整代码有些注释,希望对大家有所帮助。