场景描述
Uniapp 微信小程序项目使用的页面路由文件是Json格式的文件,cli项目在新增页面的时候需要手动添加页面路径,非常麻烦。
设计思路
通过nodejs遍历项目主包,分包路径,过滤出指定页面文件(如:index.vue)路径,然后整理重新写入pages.json文件,将方法封装成打包器插件,在指定路径下文件有修改时重新执行方法,热更新。
自定义路由存在的目的是为了兼容名称非 index.vue 的页面文件
具体实现
Tips: 本文仅介绍 Vue3 vite cli构建的项目 Vue2 webpack 版本详见
由HBuilderX 构建的项目在添加文件时会自动在pages.json中添加页面路径
项目目录
主函数 (build-routes.js)
import fs from "fs"
import path from "path"
import lodash from "lodash"
// 函数传入一个options 包含四个属性
// {
// mainRootPath: "", 主包根路径(作者习惯将主包放进main中统一格式)
// subRootPath: [], 分包根路径(也就是pages.json中 subPackages[] -> root)
// pagesJsonPath: '', pages.json 文件的路径 一般不会变化
// routesPath: '' 自定义路由文件的路径
// }
// 所有路径统一使用相对路径
function BuildRoutes(options) {
const {
mainRootPath = "main",
subRootPath = [],
pagesJsonPath = "src/pages.json",
routesPath = "src/routes/index.json"
} = options //默认属性值
const customRoutes = readExportedData(path.join(__dirname, routesPath)) // 提取自定义路由
//主包文件路径集合
const _main = getAllFiles(path.join(__dirname, "src/" + mainRootPath), [], "src/").map((item) => {
return {
path: item
}
})
//分包文件路径集合
const subPackages = subRootPath.map((_path) => {
return {
root: _path,
pages: getAllFiles(path.join(__dirname, "src/" + _path), [], `src/${_path}/`).map((item) => {
return {
path: item
}
})
}
})
// 合并主包、分包路由,判断有没有分包路由 没就不加了
const routes = {
pages: _main
}
if (!lodash.isEmpty(subPackages)) {
routes.subPackages = subPackages
}
//合并遍历出来的文件路由和自定义路由,后续的所有自定义路由修改会同步到pages.json中
const result = mergeObjects(routes, customRoutes)
//pages.json文件权限管理,防止不小心修改
fs.chmodSync(path.join(__dirname, pagesJsonPath), 0o666)
fs.writeFileSync(
path.join(__dirname, pagesJsonPath),
`//此文件按照目录结构由build-routes.js自动生成,请勿手动修改!\n//如需单独修改页面样式请在/${routesPath}中添加需要修改的页面内容!\n
${JSON.stringify(result, null, 2)}`,
"utf-8"
)
fs.chmodSync(path.join(__dirname, pagesJsonPath), 0o444)
}
function getAllFiles(dirPath, arrayOfFiles, split) {
let files = []
files = fs.readdirSync(dirPath)
files.forEach(function (file) {
const filePath = path.join(dirPath, file)
if (fs.statSync(filePath).isDirectory()) {
arrayOfFiles = getAllFiles(filePath, arrayOfFiles, split)
} else if (path.basename(filePath) === "index.vue") { // 约定页面文件名字为index.vue
arrayOfFiles.push(filePath.replaceAll("\\", "/").split(split)[1].replaceAll(".vue", ""))
}
})
return arrayOfFiles
}
function readExportedData(filePath) {
const fileData = fs.readFileSync(filePath, "utf-8")
return JSON.parse(fileData)
}
// 合并函数,主包按照path来合并,分包按照root和path合并。
function mergeObjects(target, source) {
for (let key in source) {
if (key in target) {
if (typeof source[key] === "object" && !Array.isArray(source[key])) {
if (!target[key]) {
target[key] = {}
}
mergeObjects(target[key], source[key])
} else if (Array.isArray(source[key])) {
source[key].forEach((item) => {
const id = item.path || item.root
const index = target[key].findIndex((t) => t.path === id || t.root === id)
if (index >= 0) {
mergeObjects(target[key][index], item)
} else {
target[key].push(item)
}
})
} else {
target[key] = source[key]
}
} else {
target[key] = source[key]
}
}
return target
}
export default BuildRoutes
vite插件 (vite-plugin-use-callback.js)
import chokidar from "chokidar"
// 监听path路径下的文件变动,当文件发生变动时执行callback函数
export default function vitePluginUseCallback(options) {
const { path, callback } = options
let hasCalled = false
return {
name: "vite-plugin-use-callback",
enforce: "pre",
buildStart: (options) => {
if (!hasCalled && process.env.NODE_ENV === "development") {
callback()
const watcher = chokidar.watch(path)
watcher.on("all", (file) => {
callback()
})
hasCalled = true
}
}
}
}
使用(vite.config.js)
import { defineConfig } from "vite"
import uni from "@dcloudio/vite-plugin-uni"
import vitePluginUseCallback from "./plugins/vite-plugin-use-callback.js"
import path from "path"
import buildRoutes from "./build-routes.js"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
uni(),
vitePluginUseCallback({
path: path.join(__dirname, "./src"),
callback: () => {
buildRoutes({
mainRootPath: "main",
subRootPath: []
})
}
})
]
})