前言
之前写博客时,都是一个功能一个文件夹,需要呈现哪个功能时就引入一下,再注释掉其他的引入。现在打算升级一下,引入路由,并根据目录下的文件自动生成路由表
,这样就只要在对应的文件夹下面穿创建文件,路由表就会自动更新。
正文
原理很简单,只要在编译前去检查路由表是否需要更新,需要更新就运行更新路由脚本
。
这就是自动生成的效果
配置webpack
因为我们需要在编译前去检查路由表是否需要更新,所以我们需要去写一个webpack
的plugin
干这件事,就需要我们能够修改webpack
配置。
修改webpack
配置不会的话,可以去看看 React配置路径别名、ESLint设置、git commit前规范检查 - 掘金
配置文件内容修改为
js
//craco.config.js
const path = require('path');
const { spawnSync } = require('child_process');
class MyBeforeCompilePlugin {
apply(compiler) {
compiler.hooks.beforeCompile.tapAsync('beforeCompile', (params, callback) => {
const scriptPath = path.resolve(__dirname, 'beforeCompileScript.js');
const result = spawnSync('node', [scriptPath]);
if (result.error) {
console.error('运行脚本时出错:', result.error);
} else {
console.log('beforeCompile 脚本运行成功!');
}
callback()
});
}
}
module.exports = {
webpack: {
plugins: [
new MyBeforeCompilePlugin()
],
alias: {
'@': path.resolve(__dirname, 'src')
},
},
devServer: {
port: 8080,
},
};
确定路由生成规则
- 只要在
src/pages
下创建XXX
目录,并在下创建index.tsx
文件,就会生成对应路由。 - 可能还有其他的需求,还可以在目录下创建
pageConfig.ts
(这个名称可以随便取,但是要跟脚本中的名称对应),用来配置页面中其他的参数,根据需求来。这边我是生成了一个标题的字段,比如
js
//src/pages/home/pageConfig.ts
export default {
title: "首页",
}
编写脚本
在scr
下创建router
文件夹,并在下面创建index.js
文件 我们脚本修改的是childrenList
的值。
js
import App from '../App.tsx'
import { createBrowserRouter } from 'react-router-dom'
let childrenList = [];
let children = childrenList.map((item) => {
return {
element: item.element,
path: item.path,
}
})
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children
}
])
let routeList = childrenList
export {
routeList
}
export default router
在根目录下创建beforeCompileScript.js
脚本文件
js
let path = require('path');
const fs = require('fs');
/**
* @description 读取文件夹下面的文件夹名称
* @param path 文件夹路径 string
* @returns 返回一个promise对象,resolve返回文件夹名称数组
*/
const readdir = (path) => {
return new Promise((resolve, reject) => {
fs.readdir(path, { withFileTypes: true }, (err, files) => {
if (err) {
reject(err);
}
const folderNames = files.filter(file => file.isDirectory()).map(folder => folder.name);
return resolve(folderNames);
})
})
}
/**
* @description 同步读取文件内容
* @param path 文件路径 string
* @returns 返回文件内容 string
*/
const readFile = (path) => {
return fs.readFileSync(path, (err, data) => {
if (err) {
return '';
}
return data;
})
}
/** 路由文件地址*/
let routerPath = path.join(__dirname, 'src', 'router', 'index.js');
// 读取文件内容
fs.readFile(routerPath, 'utf-8', (err, data) => {
if (err) {
console.error('读取文件时出错:', err);
return;
}
let startInd = data.indexOf('childrenList');
//从开始下标开始寻找
let resultStartInd = data.indexOf('[', startInd);
let resultEndInd = data.indexOf(']', startInd);
/** 需要替换的str*/
let replaceStr = data.substring(resultStartInd, resultEndInd + 1);
/** 原本文件中的路由列表*/
let originalList = replaceStr
originalList = originalList.replace(/\</g, '"<')
originalList = originalList.replace(/\/\>/g, '/>"')
originalList = JSON.parse(originalList);
/** 是否需要更新路由*/
let isChange = false;
/** 替换的路由列表*/
let replacingContent = [];
readdir(path.join(__dirname, 'src', 'pages')).then(res => {
let content = {}
res.forEach((name) => {
content = {};
try {
let filePath = path.join(__dirname, 'src', 'pages', name, 'pageConfig.ts')
let fileData = readFile(filePath).toString();
let startInd = fileData.indexOf('{')
let endInd = fileData.lastIndexOf('}');
let resultData = fileData.substring(startInd, endInd + 1);
//正则去除空白符
resultData = resultData.replace(/\s/g, '')
//正则 ' 变为 "
resultData = resultData.replace(/\'/g, '"')
//正则去掉}前面的,
resultData = resultData.replace(/\,(?=\})/g, '')
//为key加上双引号
resultData = resultData.replace(/([a-zA-Z0-9]+?):/g, '"$1":');
resultData = JSON.parse((resultData))
content = {
...resultData,
path: `${name}`,
element: `<${name.charAt(0).toUpperCase() + name.slice(1)} />`,
}
replacingContent.push(content);
} catch (error) {
content = {
path: `${name}`,
element: `<${name.charAt(0).toUpperCase() + name.slice(1)} />`,
}
replacingContent.push(content);
}
})
return res
}).then((res) => {
if (replacingContent.length !== originalList.length) {
isChange = true;
}
if (!isChange) {
for (let i = 0; i < replacingContent.length; i++) {
let keys = Object.keys(replacingContent[i]);
/** 需要检查的key列表*/
let checkKeys = keys.filter((item) => {
return !['element', 'path'].includes(item)
})
/** 检查长度是否一样*/
if (keys.length != Object.keys(originalList[i]).length) {
isChange = true;
break;
}
/** 检查key对相应的value是否一致*/
for (let j = 0; j < keys.length; j++) {
if (checkKeys.includes(keys[j]) && replacingContent[i][keys[j]] != originalList[i][keys[j]]) {
isChange = true;
break;
}
}
}
}
if (isChange) {
// 替换文件内容
let updatedContent = data.replace(replaceStr, JSON.stringify(
replacingContent
));
//去除导入的组件,同时去掉回车符
updatedContent = updatedContent.replace(/import.*from.*@.*\S/g, ' ')
updatedContent = updatedContent.trim();
// import 导入组件
res.forEach((name) => {
updatedContent = `import ${name.charAt(0).toUpperCase() + name.slice(1)} from '@/pages/${name}/index.tsx'\n`.concat(updatedContent)
})
updatedContent = updatedContent.replace(/\"\</g, '<')
updatedContent = updatedContent.replace(/\>\"/g, '>')
// 将更新后的内容写入文件
fs.writeFile(routerPath, updatedContent, 'utf-8', (err) => {
if (err) {
console.error('写入文件时出错:', err);
return;
}
console.log('文件内容已成功替换!');
});
}
})
});
结语
经以上步骤,不管在运行项目还是打包项目时,都会先检查路由是否需要更新,需要则更新路由表。
不懂的可以去试试