由来
首先让我们来看看pages.json中有哪些令人感到苦恼的问题:
- 当项目中页面较多时,pages.json 文件会变得很大,不方便维护,多人协作的时候容易出现代码冲突
- 路由跳转是要写一长串的路径,如果传参多,并且不封装的情况下路径会更长
由于有这个两个问题,所以就有了封装个自动生成pages.json的插件需求,本着有需求就干的精神,开始操练起来🚗
插件
插件已开发上传到npm:
uni-vite-plugin-pages-generator:www.npmjs.com/package/uni…
大致思路
问题一:可以将pages.json中的所有配置进行文件拆分,拆分完成后再进行自动合并,这样大致出来的目录配置和文件代码就是:
// 主包页面配置
module.exports = [
{
path: "pages/home/home",
style: {
navigationBarTitleText: "首页"
}
},
];
这里就可以看出可以将pages.json中的配置分开写,pages和subPackage目录分别写的是主包页面和子包页面,页面的话可能比较多,所以是文件夹,而其他的配置则直接写配置即可如tabbar:
// tabbar配置
module.exports = {
color: "#323233",
selectedColor: "#1989fa",
list: [
{
pagePath: "pages/home/home",
iconPath: "static/tabbar/btn_home1.png",
selectedIconPath: "static/tabbar/btn_home2.png",
text: "首页"
},
{
pagePath: "pages/me/me",
iconPath: "static/tabbar/btn_mine1.png",
selectedIconPath: "static/tabbar/btn_mine2.png",
text: "我的"
}
]
};
问题二:将读取到的文件除了生成pages.json再生成一个映射文件,大致如下:
const index = {
home: "/pages/home/home",
me: "/pages/me/me"
};
const home = {
householdsList: "/subPackages/home/pages/householdsList",
householdsDetail: "/subPackages/home/pages/householdsDetail",
peasantHousehold: "/subPackages/home/pages/peasantHousehold"
};
const pages = { ...index, ...home };
export default pages;
有了大致的思路就可以开搞了😎😎:
实现步骤
- 新建插件项目,如果对npm插件不了解怎么开发可以看:juejin.cn/post/684490…
- 在src下新建一个文件generatePagesJson.js,用于生成pages.json,代码如下:
const fs = require("fs");
const path = require("path");
// 读取主包配置文件
const indexPath = path.join(process.cwd(), process.argv[2], "/pages");
const indexFiles = fs.readdirSync(indexPath);
let indexConfig = [];
for (const file of indexFiles) {
if (file.endsWith(".ts")) {
const module = require(path.join(indexPath, file));
indexConfig = module;
}
}
// 读取子包配置文件
const subPackagesPath = path.join(process.cwd(), process.argv[2], "/subPackages");
const subPackagesConfig = [];
if (fs.existsSync(subPackagesPath)) {
const subPackagesFiles = fs.readdirSync(subPackagesPath);
for (const file of subPackagesFiles) {
if (file.endsWith(".ts")) {
const moduleName = path.basename(file, ".ts");
const modulePath = path.join(subPackagesPath, moduleName + ".ts");
const module = require(modulePath);
const subPackage = {
root: `subPackages/${moduleName}`,
name: moduleName,
pages: module
};
subPackagesConfig.push(subPackage);
}
}
}
// 生成 pages.json 配置
const pagesConfig = {
pages: indexConfig,
// 添加子包配置
subPackages: subPackagesConfig
};
// 读取和生成其他配置文件
const configPath = path.join(process.cwd(), process.argv[2]);
const configFiles = fs.readdirSync(configPath);
for (const file of configFiles) {
if (file.endsWith(".ts") && file !== "index.ts") {
const configName = path.basename(file, ".ts");
const filePath = path.join(configPath, configName + ".ts");
const config = require(filePath);
pagesConfig[configName] = config;
}
}
// 写入 pages.json 文件
fs.writeFileSync(path.join(process.cwd(), "/src/pages.json"), JSON.stringify(pagesConfig, null, 2));
- 在src下新建一个文件generatePages.js,用于生成路由映射文件,代码如下:
const fs = require("fs");
const path = require("path");
// 读取主包配置文件
const indexPath = path.join(process.cwd(), process.argv[2], "/pages");
const indexFiles = fs.readdirSync(indexPath);
const pagesConfig = {
index: {}
};
for (const file of indexFiles) {
if (file.endsWith(".ts")) {
const module = require(path.join(indexPath, file));
for (const router of module) {
const paths = router.path.split("/");
const pageName = paths[paths.length - 1];
pagesConfig.index[pageName] = "/" + router.path;
}
}
}
// 读取子包配置文件
const subPackagesPath = path.join(process.cwd(), process.argv[2], "/subPackages");
if (fs.existsSync(subPackagesPath)) {
const subPackagesFiles = fs.readdirSync(subPackagesPath);
for (const file of subPackagesFiles) {
if (file.endsWith(".ts")) {
const moduleName = path.basename(file, ".ts");
const modulePath = path.join(subPackagesPath, moduleName + ".ts");
const module = require(modulePath);
pagesConfig[moduleName] = {};
for (const router of module) {
const paths = router.path.split("/");
const pageName = paths[paths.length - 1];
pagesConfig[moduleName][pageName] = `/subPackages/${moduleName}/${router.path}`;
}
}
}
}
// 生成 pages.json 配置
const fileContent = Object.keys(pagesConfig)
.map((key) => `const ${key} = ${JSON.stringify(pagesConfig[key], null, 2).replace(/"([^"]+)":/g, "$1:")};`)
.join("\n");
const exportContent = `const pages = { ${Object.keys(pagesConfig)
.map((key) => "..." + key)
.join(", ")} };\n\nexport default pages;\n`;
const finalContent = `// Generated by script\n\n${fileContent}\n\n${exportContent}`;
// 写入 pages.json 文件
fs.writeFileSync(path.join(process.cwd(), process.argv[2], "/action/pages.ts"), finalContent);
- 新建一个index.js文件, 在项目启动时同时跑两个脚本:
const { execSync } = require("child_process");
const path = require("path");
let count = 0;
const exec = (folderPath) => {
if (count !== 0) return;
count = count + 1;
// 此路径是在项目根目录下执行的,所以读取的为项目中的node_modules
const generatePagesJsonPath = path.join(__dirname, "./generatePagesJson.js");
const generatePagesPath = path.join(__dirname, "./generatePages.js");
execSync(`node ${generatePagesJsonPath} ${folderPath}`);
execSync(`node ${generatePagesPath} ${folderPath}`);
};
module.exports = (folderPath) => ({
name: "pages-generator",
buildStart() {
exec(folderPath);
}
});
这样就完成了代码映射文件生成和生成pages.json,插件也就写好了
- 然后将插件引入到我们的项目中,在vite.config.js文件中:
import pagesGeneratorPlugin from "vite-plugin-pages-generator";
...
plugins: [
...
pagesGeneratorPlugin("/src/router") // "/src/router"为读取配置文件路径
]
- 重启则看到自己项目生成了映射文件和pages.json
- 根据映射文件封装路由跳转,代码如下:
/**
* @Author GUAN
* @Desc 简化router跳转api
*/
import { computed } from "vue"
interface RouteParams {
[key: string]: any
}
interface BackParams {
delta?: number
data?: any
}
const routeStore: RouteParams = {}
let navigateLock = false // 处理快速点击多次
export const useActionRouter = () => {
const pages = computed(() => (uni.getStorageSync("routerPages") as Record<string, string>) || {})
const getRouteParams = (page: string): any => {
const p = routeStore[page]
if (!p) {
// 在nvue文件中无法获取到数据,通过本地存储获取
const store = uni.getStorageSync("routeStore")
return store[page]
}
return p
}
const setRouteParams = (page: keyof typeof pages.value, params?: Record<string, any>) => {
routeStore[page] = params
uni.setStorageSync("routeParams", routeStore)
}
const navigate = (page: keyof typeof pages.value, params?: Record<string, any>): Promise<any> => {
if (navigateLock) return Promise.reject("Multiple clicks")
const eventName = Math.floor(Math.random() * 1000) + new Date().getTime() + "" // 生成唯一事件名
navigateLock = true
setRouteParams(page, params)
uni.navigateTo({
url: `${pages.value[page]}?eventName=${eventName}`,
complete() {
navigateLock = false
}
})
return new Promise((resolve, reject) => {
uni.$once(eventName, resolve)
uni.$once(eventName, reject)
})
}
const redirect = (page: keyof typeof pages.value, params?: Record<string, any>): void => {
setRouteParams(page, params)
uni.redirectTo({ url: pages.value[page] })
}
const reLaunch = (page: keyof typeof pages.value, params?: Record<string, any>): void => {
setRouteParams(page, params)
uni.reLaunch({ url: pages.value[page] })
}
const switchTab = (page: keyof typeof pages.value, params?: Record<string, any>): void => {
setRouteParams(page, params)
uni.switchTab({ url: pages.value[page] })
}
const back = ({ delta = 1, data = null }: BackParams = { delta: 1, data: null }): void => {
const currentRoute = getCurrentPages().pop()
// @ts-ignore
const eventName = currentRoute?.options.eventName
uni.$emit(eventName, data)
uni.navigateBack({ delta })
}
return {
getRouteParams,
navigate,
redirect,
reLaunch,
switchTab,
back
}
}
export type RouterType = typeof useActionRouter
至此,就完成了所有功能的开发啦,可以愉快的进行路由管理了😉