UNPKG

vite-plugin-convention-routes

Version:

一个类似Umi的约定式路由Vite插件,自动根据文件结构生成路由配置,支持多级动态路由参数、布局组件、元数据和懒加载

614 lines (565 loc) 25.6 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { default: () => vitePluginConventionRoutes }); module.exports = __toCommonJS(index_exports); var import_path = __toESM(require("path"), 1); var import_fs = __toESM(require("fs"), 1); function vitePluginConventionRoutes(options = {}) { const { routesDir = "src/views", extensions = [".vue"], generateDeclaration = true, declarationPath = "src/router/routes.d.ts", verbose = false, isLazy = true, excludes = ["components"], layoutName = "_layout", metaConfigPath = "src/router/route-meta.js", enableInComponentMeta = true } = options; let config; let routesCode = ""; let projectRoot = ""; let needsUpdate = false; const componentMeta = /* @__PURE__ */ new Map(); let metaConfig = {}; function extractMetaFromComponent(code) { const metaMatch = code.match(/\/\*\s*route-meta\s*({[^*]*})\s*\*\//); if (metaMatch && metaMatch[1]) { try { return JSON.parse(metaMatch[1]); } catch (e) { console.warn("[vite-plugin-convention-routes] \u89E3\u6790\u7EC4\u4EF6\u5143\u6570\u636EJSON\u5931\u8D25:", e); } } const exportMatch = code.match(/export\s+const\s+routeMeta\s*=\s*({[\s\S]*?});/); if (exportMatch && exportMatch[1]) { try { const objectStr = exportMatch[1].trim(); const jsonLike = objectStr.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2":').replace(/:\s*'([^']*)'/g, ':"$1"').replace(/:\s*true\b/g, ":true").replace(/:\s*false\b/g, ":false").replace(/:\s*\[(.*?)\]/g, (_, p1) => { return ":" + JSON.stringify( p1.split(",").map((item) => item.trim().replace(/^['"]|['"]$/g, "")) ); }); try { return JSON.parse(jsonLike); } catch (jsonError) { console.warn("[vite-plugin-convention-routes] JSON\u89E3\u6790\u5931\u8D25\uFF0C\u4F7F\u7528\u7B80\u5355\u89E3\u6790:", jsonError); const result = {}; const props = objectStr.match(/(\w+)\s*:\s*([^,}\r\n]+)/g) || []; for (const prop of props) { const [key, valueStr] = prop.split(":").map((p) => p.trim()); if (valueStr === "true") { result[key] = true; } else if (valueStr === "false") { result[key] = false; } else if (!isNaN(Number(valueStr))) { result[key] = Number(valueStr); } else if (valueStr.startsWith("'") && valueStr.endsWith("'")) { result[key] = valueStr.slice(1, -1); } else if (valueStr.startsWith('"') && valueStr.endsWith('"')) { result[key] = valueStr.slice(1, -1); } else { result[key] = valueStr; } } return result; } } catch (e) { console.warn("[vite-plugin-convention-routes] \u89E3\u6790\u5BFC\u51FA\u7684\u5143\u6570\u636E\u5BF9\u8C61\u5931\u8D25:", e); } } return null; } const applyMetadata = (route, fullPath) => { route.meta = route.meta || {}; if (metaConfig && metaConfig[fullPath]) { route.meta = { ...route.meta, ...metaConfig[fullPath] }; if (verbose) { console.log("[vite-plugin-convention-routes] \u4ECE\u914D\u7F6E\u6587\u4EF6\u5E94\u7528\u5143\u6570\u636E:", fullPath); } } if (route.component && typeof route.component === "function") { const compStr = route.component.toString(); const importMatch = compStr.match(/import\(['"]([^'"]+)['"]\)/); if (importMatch && importMatch[1]) { const componentPath = importMatch[1]; for (const [id, meta] of componentMeta.entries()) { if (id.includes(componentPath)) { route.meta = { ...route.meta, ...meta }; if (verbose) { console.log("[vite-plugin-convention-routes] \u4ECE\u7EC4\u4EF6\u5E94\u7528\u5143\u6570\u636E:", fullPath); } break; } } } } }; return { name: "vite-plugin-convention-routes", configResolved(resolvedConfig) { config = resolvedConfig; projectRoot = config.root; if (verbose) { console.log("[vite-plugin-convention-routes] \u9879\u76EE\u6839\u76EE\u5F55:", projectRoot); console.log("[vite-plugin-convention-routes] \u8DEF\u7531\u76EE\u5F55:", routesDir); console.log("[vite-plugin-convention-routes] \u6392\u9664\u76EE\u5F55:", excludes); console.log("[vite-plugin-convention-routes] \u5E03\u5C40\u6587\u4EF6\u540D:", layoutName); } try { const metaConfigFile = import_path.default.resolve(projectRoot, metaConfigPath); if (import_fs.default.existsSync(metaConfigFile)) { try { const fileUrl = new URL(`file://${metaConfigFile.replace(/\\/g, "/")}`); import( /* @vite-ignore */ fileUrl.href ).then((module2) => { metaConfig = module2.default || module2; if (verbose) { console.log("[vite-plugin-convention-routes] \u52A0\u8F7D\u5143\u6570\u636E\u914D\u7F6E\u6210\u529F:", metaConfigFile); } }).catch((error) => { console.warn("[vite-plugin-convention-routes] \u5BFC\u5165\u5143\u6570\u636E\u914D\u7F6E\u6587\u4EF6\u5931\u8D25:", error); }); } catch (error) { console.warn("[vite-plugin-convention-routes] \u5BFC\u5165\u5143\u6570\u636E\u914D\u7F6E\u6587\u4EF6\u5931\u8D25:", error); } } else if (verbose) { console.log("[vite-plugin-convention-routes] \u5143\u6570\u636E\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728:", metaConfigFile); } } catch (error) { console.error("[vite-plugin-convention-routes] \u52A0\u8F7D\u5143\u6570\u636E\u914D\u7F6E\u5931\u8D25:", error); } }, transform(code, id) { if (enableInComponentMeta && id.endsWith(".vue") && id.includes(routesDir)) { const meta = extractMetaFromComponent(code); if (meta) { componentMeta.set(id, meta); if (verbose) { console.log("[vite-plugin-convention-routes] \u4ECE\u7EC4\u4EF6\u63D0\u53D6\u5230\u5143\u6570\u636E:", id, meta); } } } if (id.includes("router/index") && (id.endsWith(".js") || id.endsWith(".ts"))) { if (verbose) { console.log("[vite-plugin-convention-routes] \u68C0\u6D4B\u5230\u8DEF\u7531\u6587\u4EF6:", id); } const routesImportCode = ` // \u81EA\u52A8\u751F\u6210\u7684\u8DEF\u7531\u914D\u7F6E [ // \u521D\u59CB\u8DEF\u7531\u914D\u7F6E\u5C06\u5728\u8FD9\u91CC\u751F\u6210 ].concat((() => { // \u5B58\u50A8\u6240\u6709\u751F\u6210\u7684\u8DEF\u7531 const generatedRoutes = []; // \u7EA6\u5B9A\u5F0F\u8DEF\u7531\u5BFC\u5165 const pages = import.meta.glob('/${routesDir}/**/*.vue', { ${isLazy ? "" : "eager: true, "} }); // \u5B58\u50A8\u5E03\u5C40\u7EC4\u4EF6 const layouts = {}; // \u5B58\u50A8\u8DEF\u7531\u8DEF\u5F84\u4E0E\u7EC4\u4EF6\u7684\u6620\u5C04 const routeMap = {}; // \u68C0\u6D4B\u6839\u76EE\u5F55\u662F\u5426\u6709index.vue\u6587\u4EF6 let hasRootIndex = false; // \u5B9A\u4E49\u5E94\u7528\u5143\u6570\u636E\u7684\u51FD\u6570 const applyMetadata = (route, fullPath) => { // \u786E\u4FDDmeta\u5BF9\u8C61\u5B58\u5728 route.meta = route.meta || {}; // \u5E94\u7528\u5143\u6570\u636E const metaConfig = ${JSON.stringify(metaConfig)}; if (metaConfig && metaConfig[fullPath]) { route.meta = { ...route.meta, ...metaConfig[fullPath] }; } }; // \u7B2C\u4E00\u6B65\uFF1A\u6536\u96C6\u6240\u6709\u8DEF\u7531\u548C\u5E03\u5C40\u7EC4\u4EF6 Object.keys(pages).forEach((path) => { // \u6392\u9664\u6307\u5B9A\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6 const shouldExclude = ${JSON.stringify(excludes)}.some(excludeDir => path.includes(\`/${routesDir}/\${excludeDir}/\`) || path.startsWith(\`/${routesDir}/\${excludeDir}.\`) ); if (shouldExclude) { if (${verbose}) { console.log('[vite-plugin-convention-routes] \u6392\u9664\u8DEF\u7531:', path); } return; } const routePath = path .replace(new RegExp(\`^/${routesDir}/\`), '') .replace(/\\.vue$/, ''); // \u68C0\u67E5\u662F\u5426\u4E3A\u6839\u76EE\u5F55\u7684index.vue if (routePath === 'index') { hasRootIndex = true; generatedRoutes.push({ path: '/', component: ${isLazy ? "pages[path]" : "pages[path].default"}, name: 'home' }); // \u5E94\u7528\u5143\u6570\u636E\u5230\u6839\u8DEF\u7531 applyMetadata(generatedRoutes[generatedRoutes.length - 1], '/'); if (${verbose}) { console.log('[vite-plugin-convention-routes] \u6DFB\u52A0\u6839\u8DEF\u7531: / \u7EC4\u4EF6:', pages[path] ? '\u5DF2\u52A0\u8F7D' : '\u672A\u52A0\u8F7D'); } return; } // \u68C0\u67E5\u662F\u5426\u662F\u5E03\u5C40\u7EC4\u4EF6 if (routePath.endsWith(\`/${layoutName}\`)) { // \u83B7\u53D6\u5E03\u5C40\u7EC4\u4EF6\u6240\u5728\u7684\u76EE\u5F55\u8DEF\u5F84 const layoutDir = routePath.replace(new RegExp(\`/${layoutName}$\`), ''); layouts[layoutDir] = { component: ${isLazy ? "pages[path]" : "pages[path].default"}, path: layoutDir }; if (${verbose}) { console.log('[vite-plugin-convention-routes] \u68C0\u6D4B\u5230\u5E03\u5C40\u7EC4\u4EF6:', path, '\u76EE\u5F55:', layoutDir); } return; } // \u5904\u7406\u8DEF\u7531\u8DEF\u5F84\uFF0C\u5C06\u6587\u4EF6\u8DEF\u5F84\u8F6C\u6362\u4E3AURL\u8DEF\u5F84 // \u4F8B\u5982: users/[id]/index.vue -> users/:id const processedPath = routePath .replace(/index$/, '') .replace(/\\[(\\w+)\\]/g, ':$1') .replace(/\\/index$/, '') .replace(/\\//g, '/'); // \u5FFD\u7565\u5DF2\u7ECF\u5904\u7406\u7684\u9996\u9875\u8DEF\u7531 if (processedPath === '') return; // \u5B58\u50A8\u8DEF\u7531\u4FE1\u606F\uFF0C\u4FDD\u7559\u539F\u59CB\u8DEF\u5F84\u4EE5\u4FBF\u540E\u7EED\u5904\u7406 routeMap[routePath] = { path: processedPath === '' ? '/' : \`/\${processedPath}\`, component: ${isLazy ? "pages[path]" : "pages[path].default"}, name: processedPath.replace(/\\//g, '-').replace(/:/g, '').replace(/^-|-$/g, '') || 'home', rawPath: routePath, // \u4FDD\u5B58\u539F\u59CB\u8DEF\u5F84 originalFilePath: path // \u4FDD\u5B58\u539F\u59CB\u6587\u4EF6\u8DEF\u5F84\uFF0C\u7528\u4E8E\u786E\u4FDD\u7EC4\u4EF6\u5F15\u7528\u6B63\u786E }; }); // \u5982\u679C\u6CA1\u6709\u68C0\u6D4B\u5230\u6839\u76EE\u5F55\u7684index.vue\uFF0C\u5219\u6DFB\u52A0\u4E00\u4E2A\u9ED8\u8BA4\u7684\u6839\u8DEF\u7531 if (!hasRootIndex && Object.keys(layouts).length > 0) { generatedRoutes.push({ path: '/', redirect: '/' + Object.keys(layouts)[0] }); } // \u7B2C\u4E8C\u6B65\uFF1A\u5904\u7406\u5E03\u5C40\u7EC4\u4EF6\u548C\u5D4C\u5957\u8DEF\u7531 Object.keys(routeMap).forEach(routePath => { const route = {...routeMap[routePath]}; // \u521B\u5EFA\u526F\u672C\uFF0C\u907F\u514D\u4FEE\u6539\u539F\u59CB\u5BF9\u8C61 // \u68C0\u67E5\u8DEF\u7531\u662F\u5426\u5E94\u8BE5\u5D4C\u5957\u5728\u5E03\u5C40\u7EC4\u4EF6\u4E2D let parentFound = false; // \u4ECE\u6700\u957F\u7684\u5E03\u5C40\u8DEF\u5F84\u5F00\u59CB\u5339\u914D\uFF0C\u786E\u4FDD\u6700\u6DF1\u5C42\u7684\u5E03\u5C40\u4F18\u5148 Object.keys(layouts) .sort((a, b) => b.length - a.length) .forEach(layoutPath => { // \u5982\u679C\u8DEF\u7531\u8DEF\u5F84\u4EE5\u5E03\u5C40\u8DEF\u5F84\u5F00\u5934\uFF0C\u5E76\u4E14\u4E0D\u662F\u5E03\u5C40\u8DEF\u5F84\u672C\u8EAB if (routePath.startsWith(layoutPath + '/') && routePath !== layoutPath && !parentFound) { parentFound = true; // \u786E\u4FDD\u5E03\u5C40\u8DEF\u7531\u5DF2\u6DFB\u52A0\u5230routes\u6570\u7EC4 let layoutRoute = generatedRoutes.find(r => { return r.path === '/' + layoutPath || (layoutPath === '' && r.path === '/'); }); if (!layoutRoute) { // \u521B\u5EFA\u5E03\u5C40\u8DEF\u7531 layoutRoute = { path: layoutPath === '' ? '/' : '/' + layoutPath, component: layouts[layoutPath].component, children: [] }; // \u6DFB\u52A0\u5230routes\u6570\u7EC4 generatedRoutes.push(layoutRoute); // \u5E94\u7528\u5143\u6570\u636E applyMetadata(layoutRoute, layoutRoute.path); if (${verbose}) { console.log('[vite-plugin-convention-routes] \u521B\u5EFA\u5E03\u5C40\u8DEF\u7531:', layoutRoute.path, '\u7EC4\u4EF6:', layouts[layoutPath].component ? '\u5DF2\u52A0\u8F7D' : '\u672A\u52A0\u8F7D'); } } // \u786E\u4FDD\u5E03\u5C40\u8DEF\u7531\u6709children\u5C5E\u6027 if (!layoutRoute.children) { layoutRoute.children = []; } // \u5C06\u5F53\u524D\u8DEF\u7531\u6DFB\u52A0\u4E3A\u5E03\u5C40\u8DEF\u7531\u7684\u5B50\u8DEF\u7531 // \u8C03\u6574\u5B50\u8DEF\u7531\u7684\u8DEF\u5F84\uFF0C\u79FB\u9664\u7236\u8DEF\u7531\u90E8\u5206 const childPath = routePath.substring(layoutPath.length + 1); // \u5904\u7406\u5B50\u8DEF\u7531\u8DEF\u5F84 // 1. \u5C06[param]\u8F6C\u6362\u4E3A:param // 2. \u79FB\u9664\u672B\u5C3E\u7684index let processedChildPath = childPath .replace(/\\[(\\w+)\\]/g, ':$1') .replace(/\\/index$/, '') .replace(/index$/, ''); // \u786E\u4FDD\u52A8\u6001\u53C2\u6570\u8DEF\u7531\u6B63\u786E\u5904\u7406 if (processedChildPath.includes('[') && processedChildPath.includes(']')) { processedChildPath = processedChildPath.replace(/\\[(\\w+)\\]/g, ':$1'); } // \u5B50\u8DEF\u7531\u8DEF\u5F84\u4E0D\u5E94\u8BE5\u4EE5\u659C\u6760\u5F00\u5934 route.path = processedChildPath === '' ? '' : processedChildPath; // \u786E\u4FDD\u7EC4\u4EF6\u4FE1\u606F\u88AB\u4FDD\u7559 if (!route.component && routeMap[routePath].component) { route.component = routeMap[routePath].component; } else if (!route.component && routeMap[routePath].originalFilePath) { // \u5982\u679C\u7EC4\u4EF6\u4E22\u5931\uFF0C\u5C1D\u8BD5\u4F7F\u7528\u539F\u59CB\u6587\u4EF6\u8DEF\u5F84\u91CD\u65B0\u83B7\u53D6 route.component = ${isLazy ? "pages[routeMap[routePath].originalFilePath]" : "pages[routeMap[routePath].originalFilePath].default"}; } // \u66F4\u65B0\u8DEF\u7531\u540D\u79F0\uFF0C\u79FB\u9664\u5192\u53F7 if (route.name) { route.name = route.name.replace(/:/g, ''); } // \u5220\u9664rawPath\u5C5E\u6027\uFF0C\u907F\u514DVue Router\u8B66\u544A delete route.rawPath; if (${verbose}) { console.log('[vite-plugin-convention-routes] \u6DFB\u52A0\u5B50\u8DEF\u7531:', route.path, '\u5230\u5E03\u5C40:', layoutRoute.path, '\u7EC4\u4EF6:', route.component ? '\u5DF2\u52A0\u8F7D' : '\u672A\u52A0\u8F7D'); } // \u5E94\u7528\u5143\u6570\u636E const fullPath = layoutRoute.path === '/' ? '/' + route.path : layoutRoute.path + '/' + route.path; applyMetadata(route, fullPath); // \u6700\u540E\u68C0\u67E5\u786E\u4FDD\u8DEF\u7531\u6709\u7EC4\u4EF6 if (!route.component) { console.warn(\`[vite-plugin-convention-routes] \u8B66\u544A: \u8DEF\u7531 \${route.path} \u6CA1\u6709\u7EC4\u4EF6\`); } layoutRoute.children.push(route); } }); // \u5982\u679C\u6CA1\u6709\u627E\u5230\u7236\u5E03\u5C40\uFF0C\u5219\u6DFB\u52A0\u5230\u9876\u5C42\u8DEF\u7531 if (!parentFound) { // \u5220\u9664rawPath\u5C5E\u6027\uFF0C\u907F\u514DVue Router\u8B66\u544A delete route.rawPath; generatedRoutes.push(route); // \u5E94\u7528\u5143\u6570\u636E\u5230\u8DEF\u7531 applyMetadata(route, route.path); } }); // \u6700\u7EC8\u68C0\u67E5\uFF0C\u786E\u4FDD\u6240\u6709\u8DEF\u7531\u90FD\u6709\u7EC4\u4EF6 const checkRouteComponents = (routes) => { routes.forEach(route => { // \u5FFD\u7565\u91CD\u5B9A\u5411\u8DEF\u7531 if (route.redirect) return; // \u68C0\u67E5\u5F53\u524D\u8DEF\u7531\u662F\u5426\u6709\u7EC4\u4EF6 if (!route.component) { console.warn(\`[vite-plugin-convention-routes] \u8B66\u544A: \u8DEF\u7531 \${route.path} \u6CA1\u6709\u7EC4\u4EF6\`); } // \u9012\u5F52\u68C0\u67E5\u5B50\u8DEF\u7531 if (route.children && route.children.length > 0) { checkRouteComponents(route.children); } }); }; // \u6267\u884C\u68C0\u67E5 checkRouteComponents(generatedRoutes); // \u8F93\u51FA\u751F\u6210\u7684\u8DEF\u7531\u914D\u7F6E\uFF0C\u65B9\u4FBF\u8C03\u8BD5 if (${verbose}) { // \u521B\u5EFA\u4E00\u4E2A\u4E0D\u5305\u542B\u51FD\u6570\u7684\u8DEF\u7531\u914D\u7F6E\u526F\u672C\u7528\u4E8E\u65E5\u5FD7\u8F93\u51FA const routesForLog = JSON.parse(JSON.stringify(generatedRoutes, (key, value) => { // \u5C06\u7EC4\u4EF6\u51FD\u6570\u8F6C\u6362\u4E3A\u6807\u8BB0\uFF0C\u4EE5\u4FBF\u5728\u65E5\u5FD7\u4E2D\u663E\u793A if (key === 'component' && typeof value === 'function') { return '() => Component'; // \u7528\u5B57\u7B26\u4E32\u66FF\u4EE3\u51FD\u6570 } return value; })); console.log('[vite-plugin-convention-routes] \u751F\u6210\u7684\u8DEF\u7531\u914D\u7F6E:', JSON.stringify(routesForLog, null, 2)); } return generatedRoutes; })()) `; routesCode = routesImportCode; const routesRegex = /(const\s+routes\s*(?::\s*RouteRecordRaw\[\])?\s*=\s*)\[\s*\]/; if (routesRegex.test(code)) { return code.replace(routesRegex, (_match, p1) => { return p1 + routesImportCode.trim().replace(/;$/, ""); }); } return null; } return null; }, async closeBundle() { if (generateDeclaration && routesCode) { const declarationContent = `// \u81EA\u52A8\u751F\u6210\u7684\u8DEF\u7531\u7C7B\u578B\u58F0\u660E import { RouteRecordRaw } from 'vue-router'; declare const routes: RouteRecordRaw[]; export default routes; `; if (verbose) { console.log( "[vite-plugin-convention-routes] \u751F\u6210\u8DEF\u7531\u58F0\u660E\u6587\u4EF6:", declarationPath ); } try { const fullPath = import_path.default.resolve(projectRoot, declarationPath); const dirPath = import_path.default.dirname(fullPath); if (!import_fs.default.existsSync(dirPath)) { import_fs.default.mkdirSync(dirPath, { recursive: true }); } import_fs.default.writeFileSync(fullPath, declarationContent, "utf-8"); if (verbose) { console.log( "[vite-plugin-convention-routes] \u8DEF\u7531\u58F0\u660E\u6587\u4EF6\u5DF2\u751F\u6210:", fullPath ); } } catch (error) { console.error( "[vite-plugin-convention-routes] \u751F\u6210\u58F0\u660E\u6587\u4EF6\u5931\u8D25:", error ); } } }, // 提供虚拟模块,可以通过import导入生成的路由 resolveId(id) { if (id === "virtual:convention-routes") { return "\0virtual:convention-routes"; } return null; }, load(id) { if (id === "\0virtual:convention-routes") { return routesCode || "export default [];"; } return null; }, // 监听路由文件变化,发送HMR更新而非重启服务器 configureServer(server) { const routesGlob = `${routesDir}/**/*.vue`; let hmrTimeout = null; server.watcher.add(routesGlob); const handleRouteChange = (file) => { if (file.includes(routesDir) && extensions.some((ext) => file.endsWith(ext))) { if (verbose) { console.log("[vite-plugin-convention-routes] \u68C0\u6D4B\u5230\u8DEF\u7531\u6587\u4EF6\u53D8\u5316:", file); } needsUpdate = true; if (hmrTimeout) { clearTimeout(hmrTimeout); } hmrTimeout = setTimeout(() => { const routerFiles = ["router/index.js", "router/index.ts", "src/router/index.js", "src/router/index.ts"]; const routerModules = []; routerFiles.forEach((file2) => { const mods = server.moduleGraph.getModulesByFile(file2); if (mods) { routerModules.push(...Array.from(mods)); } }); const virtualMods = server.moduleGraph.getModulesByFile("\0virtual:convention-routes"); if (virtualMods) { routerModules.push(...Array.from(virtualMods)); } const modulesToInvalidate = new Set(routerModules); const addImporters = (mod) => { if (!mod.importers) return; for (const importer of mod.importers) { if (!modulesToInvalidate.has(importer)) { modulesToInvalidate.add(importer); addImporters(importer); } } }; routerModules.forEach((mod) => { if (mod) { addImporters(mod); } }); if (verbose) { console.log(`[vite-plugin-convention-routes] \u627E\u5230 ${modulesToInvalidate.size} \u4E2A\u9700\u8981\u66F4\u65B0\u7684\u6A21\u5757`); } if (modulesToInvalidate.size > 0) { modulesToInvalidate.forEach((mod) => { server.moduleGraph.invalidateModule(mod); }); const timestamp = Date.now(); const updates = Array.from(modulesToInvalidate).map((mod) => ({ type: "js-update", path: mod.url, acceptedPath: mod.url, timestamp })); if (verbose) { console.log("[vite-plugin-convention-routes] \u53D1\u9001HMR\u66F4\u65B0:", updates.length); } server.ws.send({ type: "update", updates }); setTimeout(() => { if (verbose) { console.log("[vite-plugin-convention-routes] \u53D1\u9001\u5168\u9875\u9762\u5237\u65B0"); } server.ws.send({ type: "full-reload", path: "*" }); }, 100); } else { if (verbose) { console.log("[vite-plugin-convention-routes] \u672A\u627E\u5230\u76F8\u5173\u6A21\u5757\uFF0C\u53D1\u9001\u5168\u9875\u9762\u5237\u65B0"); } server.ws.send({ type: "full-reload", path: "*" }); } }, 100); } }; server.watcher.on("add", (file) => { handleRouteChange(file); }); server.watcher.on("unlink", (file) => { handleRouteChange(file); }); server.watcher.on("change", (file) => { handleRouteChange(file); }); server.middlewares.use((req, _res, next) => { if (needsUpdate && req.url && (req.url.includes("router/index") || req.url.includes("virtual:convention-routes"))) { needsUpdate = false; if (verbose) { console.log("[vite-plugin-convention-routes] \u68C0\u6D4B\u5230\u8DEF\u7531\u8BF7\u6C42\uFF0C\u89E6\u53D1\u66F4\u65B0"); } handleRouteChange("virtual-trigger"); } next(); }); } }; }