auto-router-vue3
Version:
358 lines (337 loc) • 11.8 kB
JavaScript
import {compileScript, parse} from "vue/compiler-sfc"
import path from "path"
import fs from "fs"
import watch from "watch"
import {getConfig, getSrcInfo, GUID,basename} from "./comm.mjs";
let _config = getConfig();
const dirInfo = getSrcInfo();
const __filename = dirInfo.filename; // 当前文件路径
const __dirname = dirInfo.dirname; // 当前文件所处的文件夹路径
const __dir = dirInfo.dir; // 执行命令时的路径
let pages = path.join(__dir, "/src",_config.pagePath);
let files;
const routeDir = path.join(__dir,"src/router");
const ignoreDirs = /(components|utils)/;
export function updateConfigInfo(){
_config = getConfig();
pages = path.join(__dir, "/src",_config.pagePath);
}
/**
* @desc 递归遍历文件夹及文件 并做相应的处理
* 规则:
*
* 1. 页面局部组件 , 若放在单文件夹中 , 需放在在目录下的components文件夹中
*
* @param fls
* @param p
*/
let routes = []; // 路由列表
let routesInfo = {}; // 路由详情
export function loopDir(fls = [],p){
if (fls.length === 0)return;
fls.forEach(item=>{
const fullPath = path.join(p,item); // 完整路径
const isDir = fs.statSync(fullPath).isDirectory(); // 是否为文件夹类型
if (isDir && !ignoreDirs.test(item.toLowerCase())){
const child = fs.readdirSync(fullPath) || [];
loopDir(child,fullPath);
}else {
// 获取后缀
const extname = path.extname(fullPath);
// 只处理vue文件
if (extname === ".vue"){
let routeStr = analysisRouteConfig(fullPath)
routes.push(routeStr)
routesInfo[fullPath] = {
data:routeStr,
index:routes.length - 1,
}
}
}
})
}
export function getConfigStr(content,setup = false){
let lc = 0;
let power = false;
let strCfg = "";
let configReg = setup?/_config\s*(:\s*\w+)?\s*=\s*/:/_config\s*:\s*/;
// 获取_config配置
try {
if (setup)
content = content.replace(configReg,"_config = ")
content.split("\n").forEach((item)=>{
// _config 字段
if (!power && configReg.test(item)){
power = true;
}
if (!power)return ;
let strlen = item.length;
strCfg += (item+"\n");
for (let i = 0 ;i < strlen;i++){
let str = item[i];
if (str === "{"){
lc ++;
}
if(str === "}"){
lc --;
}
}
if (lc === 0){
power = false;
throw "break";
}
})
}catch (e){
if (e==="break")
return strCfg;
return null;
}
}
/**
* @desc 解析vue文件
* @param filepath 文件路径
*/
export function analysisVue(filepath) {
if (!filepath)return ;
const file = fs.readFileSync(filepath,{encoding:"utf-8"})
const parseVue = parse(file);
try {
// 因为在有些特殊情况下 , 比如在script标签中没有任何代码的情况下 , compileScript函数会报错 , 所以放在try中
let {content,setup} = compileScript(parseVue.descriptor,{
filename:path.basename(filepath),
id:GUID(),
});
let strCfg = getConfigStr(content,setup);
if (setup){
strCfg = strCfg.replace(/^(const|let).*_config\s*=\s*/,'return');
return (new Function(`${strCfg}`))();
}
else{
return (new Function(`return {${strCfg}}`))()._config;
}
}catch (e){
console.log("analysis vue is null")
return null;
}
}
/**
* @desc 解析路由配置
* @param filepath 文件路径
*/
function analysisRouteConfig(filepath){
if (!filepath)return;
let config = analysisVue(filepath);
let rePath = filepath.replace(pages,"/"+_config.pagePath).replaceAll("\\","/"); // 相对路径
let routePath = rePath.replace("/"+_config.pagePath,"").replace(".vue",""); // 路由路径
let routePathArr = routePath.split("/"); // 相对路径转数组
let res = "";
const setRoute = (r)=>{
let res = null;
if(!r){
res = {
path:routePath
}
}else if (r.path){ // 有path的情况
res = r;
}else if (r.name != null){ // 没有path 有name的情况
let len = routePathArr.length;
routePathArr[len - 1] = encodeURI(r.name);
if (routePathArr[len-1] === "")routePathArr.pop();
res = Object.assign({
path:routePathArr.join("/")
},r);
}else
res = Object.assign({
path:routePath
},r);
res["component"] = `$[()=>import('@${rePath}')]$`;
return JSON.stringify(res).replace('"$[', "").replace(']$"', "");
}
// 没有配置config的情况 or 没有配置route
if (!config || !config.route){
return setRoute(null);
}else if(config.route){
if(Array.isArray(config.route)){
let result = [];
config.route.forEach(item=>{
result.push(setRoute(item));
})
res = result.join(",\n");
}else res = setRoute(config.route);
return res;
}
}
/**
* @desc 写入路由
*/
export function writeRouter(){
let config = fs.readFileSync(path.join(basename,"template/route.js"),{encoding:"utf-8"});
if (!fs.existsSync(routeDir))
fs.mkdirSync(routeDir);
if (!fs.existsSync(routeDir+"/index.js"))
fs.writeFileSync(routeDir+"/index.js",config,{encoding:"utf-8"});
else {
let index = fs.readFileSync(path.join(routeDir,"index.js"),{encoding:"utf-8"});
if (!/import\s+\w+\s+from\s+"[\w.\/]*config\.js"/.test(index)){
fs.writeFileSync(routeDir+"/index.js","import config from \"./config.js\"\n"+index,{encoding:"utf-8"})
}
}
fs.writeFileSync(routeDir+"/config.js",`export default [\n\t${routes.join(",\n\t")}\n]`,{encoding:"utf-8"});
}
class CURD{
queue = [];
constructor() {
}
/**
* @desc 当文件被更改 , 执行更新操作
* @param filePath
*/
update(filePath) {
console.log("\n [auto-router] update rending...");
let prev = routesInfo[filePath]["data"]?.replace(/(\n)/g,"");
let cur = analysisRouteConfig(filePath);
let cur_cs = cur.replace(/(\n)/g,"");
const index = routesInfo[filePath]["index"];
if(prev !== cur_cs){
routes[index] = cur;
routesInfo[filePath]["data"] = cur;
writeRouter();
console.log("[auto-router] updated:",filePath);
}
}
/**
* @desc 执行删除操作
* @param filePath
* @param cur
*/
delete(filePath,cur){
console.log("\n [auto-router] delete rending...");
let keys = Object.keys(routesInfo);
for (let key of keys){
if (key.indexOf(filePath) > -1){
let index = routesInfo[key].index;
routes[index] = null;
this.queue.push(index);
delete routesInfo[key];
}
}
writeRouter();
console.log("[auto-router] deleted:",filePath);
}
/**
* @desc 执行新增操作
* @param filePath
* @param cur
*/
create(filePath,cur){
console.log("\n [auto-router] create rending...");
const res = this._baseLogic(filePath,cur);
if (!res)return;
res.forEach(item=>{
// 队列长度
const q_len = this.queue.length;
// 判定队列是否为空 , 如果不为空 , 则出队,否则返回数组长度
// 大致意思是 , 如果routes有空值, 则插入,没有控制则push
const index = q_len>0?this.queue.shift():routes.length;
if (routes[index] != null)
loopDir(files,pages);
else if(routesInfo[item]){
// ...
}else{
const routeStr = analysisRouteConfig(item);
routesInfo[item] = {
data: routeStr,
index
}
routes[index] = routeStr;
}
})
writeRouter();
console.log("\n [auto-router] created:",filePath);
}
/**
* @desc 主要用于判定路径是否是目录,
如果是目录则深度遍历目录下有没有vue文件 ,
如果是vue文件则返回路径
* @param dirPath
* @param cur
* @return {Array|*}
* @private
*/
_dirHasFiles(dirPath,cur){
if (!cur.isDirectory() && path.extname(dirPath) === '.vue')return [dirPath];
let q = [dirPath];
let res = [];
while (q.length){
// 当前文件夹
let c = q.shift();
let f = fs.readdirSync(c);
for (let i of f){
const fullPath = path.join(c,i);
if (fs.statSync(fullPath).isDirectory())q.push(fullPath);
else if(path.extname(fullPath) === '.vue'){
res.push(fullPath);
}
}
}
return res;
}
_baseLogic(filePath,cur){
if(ignoreDirs.test(filePath.toLowerCase()))return null;
const res = this._dirHasFiles(filePath,cur);
if (res.length === 0)return null;
return res;
}
}
/**
* @desc 监听pages目录
*/
export function watchPages(){
try {
files = fs.readdirSync(pages);
}catch (e){
throw new Error("未找到pages文件夹 , 请确认是否配置正确。 尝试使用auto-router set -p <path> 重新设置页面目录!")
}
let curd = new CURD();
let exclude = _config.excludeReg?new RegExp(_config.excludeReg):null;
watch.watchTree(pages,{
interval:1,
ignoreDotFiles:true,
ignoreUnreadableDir:true,
ignoreNotPermitted:true,
ignoreDirectoryPattern:exclude
},function (f,cur,prev) {
if (typeof f =='string') {
if (_config.excludePath && _config.excludePath.filter(e => f.includes(path.join(e))).length)
return;
if (exclude.test(f) || /~$/.test(f))
return;
else if(_config.excludeDir && _config.excludeDir.include(f))
return;
}
if (typeof f == "object" && prev === null && cur === null) {
renderAll()
// 完成对树的遍历
} else if (prev === null) {
// f 是一个新文件
curd.create(f,cur)
} else if (cur.nlink === 0) {
// f 被移除
curd.delete(f,cur);
} else if (prev != null){
curd.update(f);
}
})
}
// 全部渲染pages
export function renderAll() {
console.log("\n[auto-router]rending ...")
try {
files = fs.readdirSync(pages);
}catch (e){
throw new Error("未找到pages文件夹 , 请确认是否配置正确。 尝试使用auto-router set -p <path> 重新设置页面目录!")
}
loopDir(files,pages); // 轮询目录 , 生成route配置
writeRouter(); // 文件写入
console.log("\n[auto-router]render finished ...")
}