UNPKG

auto-router-vue3

Version:
400 lines (375 loc) 13.6 kB
import path from "path" import fs from "fs" import watch from "watch" import {getConfig, getJsonFile, getSrcInfo, traverseFolder, basename, excludeCheck} from "./comm.mjs"; import {analysisVue, getConfigStr} from "./v3.mjs"; const dirInfo = getSrcInfo(); const __filename = dirInfo.filename; // 当前文件路径 const __dirname = dirInfo.dirname; // 当前文件所处的文件夹路径 const __dir = dirInfo.dir; const routeDir = path.join(__dir,"src/router"); let _config = getConfig(); // 获取配置 let pagePath = path.join(__dir, "/src",_config.pagePath); export function updateConfigInfo(){ _config = getConfig(); pagePath = path.join(__dir, "/src",_config.pagePath); } /** * 将驼峰转换为中划线 * @param str * @return {string} */ function camelToDash(str) { return str.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase(); } function isAbsolute(path) { return path[0] === "/"; } // 根据路由配置获取指定的路由信息 function getRoute(route,prePath="",srcName=""){ // 如果没有配置route信息 给默认配置路由 let p = ""; if (!route){ route = {}; } if (!route.path){ p = camelToDash(srcName).toLowerCase(); }else{ p = route.path; } let res = ""; if (!isAbsolute(p)){ res = path.posix.join(prePath,p); }else res = p; route.path = res; return { name:p, route }; } let routeConfig = ` // 渲染一个组件或者一个空组件 export function renderComponent(component) { if (component) return component else return { render: (e) => e("router-view") } } ` const defaultPre = "$$default$$"; function analysisRouteConfig(callback,dir,initResult=null){ let routes = []; let timer = null; let defaultRedirect = new RegExp(_config.defaultRedirect || "^list$"); let map = {}; dir = dir || pagePath; traverseFolder(dir,function(status,info,fullPath,result){ if (status !== 1 && status !== 0)return ; if (timer){ clearTimeout(timer); timer = null; } let res = null; let data = null; // 文件夹处理 if (status === 0){ let route = getJsonFile(path.join(fullPath,"route.json")); let child = {} if (!route)return undefined; route = getRoute(route,!result?"/":result.path,info.name).route; route.component = `$[renderComponent()]$`; // 如果没有result , 可以判定为根目录 if (!result){ child = { ...route, } routes.push(child); res = child; }else { child = { ...route, } if(route.default){ result.redirect = route.path; } if (result.children){ result.children.push(child) }else { result.redirect = route.path; result.children = [child]; } } res = data = child; } // 文件处理 else if(status === 1){ let cfg = analysisVue(fullPath) || {}; let _route = cfg.route; let name = info.name.split(".vue")[0]; const setRoute = (route)=>{ let res = result; if (!res){ res = { path:"/" }; } let routeInfo = getRoute(route,res.path,info.name); route = routeInfo.route; // 当路由配置为被排除 , 则不执行后续操作 if (route.exclude)return result; let child = {}; let rePath = fullPath.replace(dir,path.join("/",_config.pagePath)).replaceAll("\\","/"); child = { ...route, component:`$[()=>import('@${rePath}')]$` } if(route.default){ res.redirect = defaultPre+route.path; }else if(!res.redirect || (!res.redirect.includes(defaultPre) && defaultRedirect.test(routeInfo.name))){ res.redirect = route.path; } if (!result){ result = child; routes.push(child); }else if (result.children){ result.children.push(child) }else{ result.children = [child]; } return child; } if (Array.isArray(_route)){ for (let i in _route){ let route = setRoute(_route[i]); } }else{ setRoute(_route) } // data = child; // 相对路径 } /*if (data) { map[fullPath] = data.path; }*/ timer = setTimeout(()=>{ callback(routes,map); },20) return res; },initResult) } class CURD{ sysConfig; constructor() { this._getConfig(); } _getConfig(){ this.sysConfig = getConfig(); this.pagePath = path.join(__dir, "/src",this.sysConfig.pagePath); } _each(route,filename,parent){ for (let i in route){ let item = route[i]; if (filename.indexOf(item.path) === 0){ if (filename!==item.path){ return this._each(item.children,filename,item); }else{ return {item,parent,index:i}; } } } return null; } // 权限鉴定 getPermission(filename){ let type = 0; if (filename.includes(".vue")) type = 1; else if (filename.includes("route.json")) type = 2; return type; } getPath(src,child=""){ let parentDir = path.dirname(src); let config = getJsonFile(path.join(src,"route.json")); let root = this.pagePath; if (root === src){ return "/"+child; } if(config == null || !config.path){ let dirName = camelToDash(src.split("\\").pop()); return this.getPath(parentDir,path.posix.join(dirName,child)) }else if (config.path && config.path[0] === "/"){ return path.posix.join(config.path , child); }else{ return this.getPath(parentDir,path.posix.join(config.path,child)); } } _getFileInfo(filename){ let sp = filename.split("\\"); let fullName = sp.pop(); // 文件全名 let name = fullName.replace(/\.\w+$/,""); // 文件名(去除后缀) let dir = sp.join("\\"); // 文件所在目录 let dirName = sp.pop(); // 文件所在目录名 let parentPath = sp.join("\\"); // 父级目录路径 return { parentPath, name, fullName, dir, dirName } } isSame(val,val1){ let v = JSON.stringify(val).replaceAll(/[\s\n]/g,""); let v1 = JSON.stringify(val1).replaceAll(/[\s\n]/g,""); return v === v1; } _renderRoute(filename,type,prevCfg,callback){ let route = null; let info = this._getFileInfo(filename); if (type === 1){ let pagePath = this.pagePath; route = analysisVue(filename); route = route?route.route:null; let pp = this.getPath(info.dir) // 如果没有配置route信息 给默认配置路由 if (!route || !route.path){ route = { path:path.posix.join(pp,camelToDash(info.name).toLowerCase()) } }else if(route.path[0] !== "/"){ route.path = path.posix.join(pp,route.path); } let rePath = filename.replace(pagePath,path.join("/",this.sysConfig.pagePath)).replaceAll("\\","/"); route.component = `$[()=>import('@${rePath}')]$`; if (this.isSame(route,prevCfg.item)){ callback(null) }else callback(route,{[filename]:route.path}); }else if (type === 2){ let prePath = this.getPath(info.parentPath); let cfg = getRoute(getJsonFile(filename),undefined,info.dirName); cfg.path = path.posix.join(prePath, cfg.path); for (let k in cfg){ prevCfg.item[k] = cfg[k]; } // 更改路由信息 prevCfg.item.children = []; analysisRouteConfig((routes,mapdb)=>{ mapdb[info.dir] = cfg.path; callback(routes,mapdb); },info.dir,prevCfg.item) } } update(filename){ let type = this.getPermission(filename); if (!type)return; let dataDir = path.join(basename,"data"); // 路由映射 let map = getJsonFile(path.join(dataDir,"map.json")); // 路由配置 let route = getJsonFile(path.join(dataDir,"route.json")); // 找到当前文件路径对应的路由 let cur = type===1?map[filename]:map[path.dirname(filename)]; // 更新前的配置 let prevCfg = this._each(route,cur); this._renderRoute(filename,type,prevCfg,(res,mapdb)=>{ if (res === null)return; if (type === 1) { prevCfg.parent.children[prevCfg.index] = res; if (res.default || prevCfg.index == 0){ prevCfg.parent.redirect = res.path; } } writeRoute(handleRoutes(route),routeConfig); map = { ...map, ...mapdb } fs.writeFileSync(path.join(dataDir,"map.json"),JSON.stringify(map),{encoding:"utf-8"}); fs.writeFileSync(path.join(dataDir,"route.json"),JSON.stringify(route),{encoding:"utf-8"}); console.log("\n[auto-router] update:",filename); }); } } function writeRoute(content,extra=""){ 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",extra+`\n export default ${content}`,{encoding:"utf-8"}); } function handleVueFile(){ traverseFolder(pagePath,function(status,info,fullPath,result){ if (status === 1){ let p = camelToDash(fullPath.split("\\").pop().replace(".vue","")); let cfgStr = getConfigStr(fs.readFileSync(fullPath,{encoding:"utf-8"}))||""; // 替换path name 为文件名 let rps = cfgStr.replace(/path:"[\w\d-]+"/,`path:"${p}"`) let text = fs.readFileSync(fullPath,{encoding:"utf-8"}); fs.writeFileSync(fullPath,text.replace(cfgStr,rps)); } }) } function handleRoutes(routes){ return JSON.stringify(routes) .replaceAll('"$[', "") .replaceAll(']$"', "") .replaceAll(defaultPre,"") /*.replaceAll("}","\n}") .replaceAll("]","\n]") .replaceAll(",\"",",\n\"") .replaceAll("[","[\n") .replaceAll("{","{\n");*/ } export function renderAll(){ console.log("\n[auto-router]: 正在渲染路由..."); analysisRouteConfig((routes,map)=>{ let str = handleRoutes(routes); let dataPath = path.join(basename,"data") if (!fs.existsSync(dataPath)) fs.mkdirSync(dataPath); // fs.writeFileSync(path.join(dataPath,"route.json"),JSON.stringify(routes),{encoding:"utf-8"}); // fs.writeFileSync(path.join(dataPath,"map.json"),JSON.stringify(map),{encoding:"utf-8"}); writeRoute(str,routeConfig); console.log("\n[auto-router]: 渲染完成!!!"); }) } /** * @desc 监听pages目录 */ export function watchPages(){ let curd = new CURD(); let exclude = _config.excludeReg?new RegExp(_config.excludeReg):null; let timer = null; watch.watchTree(pagePath,{ interval:1, ignoreDotFiles:true, ignoreUnreadableDir:true, ignoreNotPermitted:true, ignoreDirectoryPattern:exclude },function (f,cur,prev) { if (typeof f =='string'){ let check = excludeCheck(f); if (check.flag)return; if ((exclude.test(f) || /~$/.test(f)))return; } // 过滤重复调用 if (timer) { clearTimeout(timer); timer = null; } timer = setTimeout(()=>{ renderAll(); },50) }) }