import type { RouteRecordRaw } from 'vue-router'; interface ExposeItem { key: string; value: string; meta?: { title?: string; description?: string; category?: string; icon?: string; tags?: string[]; }; } /** * 递归遍历路由,提取所有需要暴露的组件 * @param routes 路由配置 * @param parentPath 父路径 * @param onlyLeafNodes 是否只展示叶子节点(默认 true,只展示最底层的子路由) */ function extractExposesFromRoutes( routes: RouteRecordRaw[], parentPath = '', onlyLeafNodes = true, ): ExposeItem[] { const exposes: ExposeItem[] = []; routes.forEach((route) => { // 构建当前路径(去掉开头的 / 和结尾的 /) const currentPath = route.path.replace(/^\//, '').replace(/\/$/, ''); const fullPath = parentPath ? `${parentPath}/${currentPath}`.replace(/\/+/g, '/') : currentPath; // 检查是否有子路由 const hasChildren = route.children && route.children.length > 0; // 判断是否应该暴露当前路由 const shouldExpose = route.component && (!onlyLeafNodes || // 如果不是只展示叶子节点,所有有组件的路由都暴露 !hasChildren); // 如果只展示叶子节点,只有没有子路由的才暴露 if (shouldExpose) { // 获取组件路径 const componentPath = getComponentPath( route.component, route.name as string, ); if (componentPath) { exposes.push({ key: `./${fullPath}`, value: componentPath, meta: route.meta as any, }); } else { console.warn( `⚠️ Cannot extract component path for route: ${route.name} (${fullPath})`, ); } } // 递归处理子路由 if (hasChildren) { const childExposes = extractExposesFromRoutes( route.children, fullPath, onlyLeafNodes, ); exposes.push(...childExposes); } }); return exposes; } /** * 获取组件路径 */ function getComponentPath(component: any, routeName?: string): string | null { // 处理异步组件 () => import(...) if (typeof component === 'function') { try { // 尝试从函数中提取 import 路径 const componentStr = component.toString(); // 🔍 调试:打印函数字符串(仅在调试时) if (process.env.DEBUG_EXPOSES === 'true' && routeName) { console.log(`\n🔍 ===== Route: ${routeName} =====`); console.log(`🔍 Component function string:`, componentStr); } // 尝试多种匹配模式 let importPath: string | null = null; // 模式 1: 标准 import('...') 或 import("...") let importMatch = componentStr.match(/import\s*\(\s*['"](.+?)['"]\s*\)/); if (importMatch && importMatch[1]) { importPath = importMatch[1]; } // 模式 2: jitiImport('...') 或其他打包工具转换的形式 if (!importPath) { importMatch = componentStr.match( /jitiImport\s*\(\s*['"](.+?)['"]\s*\)/, ); if (importMatch && importMatch[1]) { importPath = importMatch[1]; } } // 模式 3: __webpack_require__ 或其他类似的模式 if (!importPath) { importMatch = componentStr.match( /\bwebpackImport\s*\(\s*['"](.+?)['"]\s*\)/, ); if (importMatch && importMatch[1]) { importPath = importMatch[1]; } } if (importPath) { // 处理相对路径,确保以 ./src 开头 if (importPath.startsWith('../')) { const result = `./src/${importPath.replace(/\.\.\//g, '')}`; if (process.env.DEBUG_EXPOSES === 'true' && routeName) { console.log(`✅ Extracted path: ${importPath} -> ${result}`); } return result; } if (importPath.startsWith('./')) { const result = `./src/${importPath.replace('./', '')}`; if (process.env.DEBUG_EXPOSES === 'true' && routeName) { console.log(`✅ Extracted path: ${importPath} -> ${result}`); } return result; } if (process.env.DEBUG_EXPOSES === 'true' && routeName) { console.log(`✅ Extracted path: ${importPath}`); } return importPath; } else { if (process.env.DEBUG_EXPOSES === 'true' && routeName) { console.log(`❌ No import path match found`); } } } catch (error) { console.error( `Error processing component for route ${routeName}:`, error, ); } } return null; } /** * 生成 Module Federation exposes 配置 * @param routes 路由配置 * @param options 配置选项 * @param options.onlyLeafNodes 是否只展示叶子节点(默认 true,只展示最底层的子路由) */ export function generateExposesFromRoutes( routes: RouteRecordRaw[], options: { onlyLeafNodes?: boolean } = {}, ) { const { onlyLeafNodes = true } = options; const exposeItems = extractExposesFromRoutes(routes, '', onlyLeafNodes); // 转换为 exposes 对象 const exposes: Record = {}; exposeItems.forEach((item) => { exposes[item.key] = item.value; }); return { exposes, exposeItems, // 返回详细信息,可用于生成文档或其他用途 }; } /** * Rsbuild 插件:自动从路由生成 exposes 并注入到 Module Federation 配置 * @param routes 路由配置 * @param options 配置选项 * @param options.onlyLeafNodes 是否只展示叶子节点(默认 true,只展示最底层的子路由) */ export function generateExposesPlugin( routes: RouteRecordRaw[], options: { onlyLeafNodes?: boolean } = {}, ) { return { name: 'generate-exposes-plugin', apply: 'serve', setup(build: any) { // 生成 exposes 配置 const { exposes, exposeItems } = generateExposesFromRoutes( routes, options, ); // 打印生成的配置(开发时可见) console.log('📦 Auto-generated Module Federation exposes:'); console.table( exposeItems.map((item) => ({ 'Expose Key': item.key, 'Component Path': item.value, Title: item.meta?.title || '-', })), ); // 初始化钩子:修改 Module Federation 配置 build.onBeforeBuild?.(() => { // 这里可以添加构建前的逻辑 }); // 返回 exposes 配置供外部使用 return exposes; }, }; }