#!/usr/bin/env node /** * 路由表提取器 * 从格式化后的 JS 中提取 vue-router 的 routes 定义 * * 用法: node extract-router.js */ const fs = require('fs'); const path = require('path'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; function extractRouter(inputDir, outputFile) { const files = fs.readdirSync(inputDir).filter(f => f.endsWith('.formatted.js')); const routes = []; for (const file of files) { const code = fs.readFileSync(path.join(inputDir, file), 'utf-8'); try { const ast = parser.parse(code, { sourceType: 'script', plugins: ['jsx', 'typescript', 'classProperties', 'dynamicImport'], errorRecovery: true, }); traverse(ast, { // Match routes: [...] arrays containing path/component objects ArrayExpression(nodePath) { const node = nodePath.node; const hasRoutes = node.elements.some(el => el.type === 'ObjectExpression' && el.properties.some(prop => prop.key && prop.key.name === 'path') ); if (hasRoutes) { node.elements.forEach(el => { if (el.type === 'ObjectExpression') { const routeDef = {}; el.properties.forEach(prop => { if (prop.key && prop.key.type === 'Identifier') { if (prop.key.name === 'path' && prop.value.type === 'StringLiteral') { routeDef.path = prop.value.value; } if (prop.key.name === 'name' && prop.value.type === 'StringLiteral') { routeDef.name = prop.value.value; } if (prop.key.name === 'meta' && prop.value.type === 'ObjectExpression') { routeDef.meta = {}; prop.value.properties.forEach(mp => { if (mp.key && mp.key.type === 'Identifier') { routeDef.meta[mp.key.name] = mp.value.type === 'StringLiteral' ? mp.value.value : true; } }); } if (prop.key.name === 'component') { routeDef.component = prop.value.type === 'ArrowFunctionExpression' ? 'lazyImport' : (prop.value.type === 'Identifier' ? prop.value.name : 'inline'); } } }); if (routeDef.path && !routes.find(r => r.path === routeDef.path)) { routes.push(routeDef); } } }); } }, // Match path-like strings: /xxx/xxx StringLiteral(nodePath) { const value = nodePath.node.value; if (typeof value === 'string' && /^\/[a-z][a-z0-9_/-]*$/i.test(value) && value.length > 2) { const parent = nodePath.parent; if (parent.type === 'ObjectProperty' && parent.key && parent.key.name === 'path') { return; // Already handled above } if (!routes.find(r => r.path === value)) { routes.push({ path: value, source: 'stringLiteral', file: file }); } } }, }); } catch (e) { // Skip unparseable files } } const result = routes.sort((a, b) => (a.path || '').localeCompare(b.path || '')); fs.writeFileSync(outputFile, JSON.stringify(result, null, 2)); console.log(`提取完成: ${result.length} 条路由 → ${outputFile}`); // Generate Markdown route table const mdPath = outputFile.replace('.json', '.md'); let md = '# 路由表\n\n'; md += `| Path | Name | Component | Meta |\n`; md += `|------|------|-----------|------|\n`; result.forEach(r => { md += `| \`${r.path || '-'}\` | ${r.name || '-'} | ${r.component || '-'} | ${r.meta ? JSON.stringify(r.meta) : '-'} |\n`; }); fs.writeFileSync(mdPath, md); console.log(`路由表 Markdown: ${mdPath}`); } const args = process.argv.slice(2); if (args.length < 2) { console.log('用法: node extract-router.js '); process.exit(1); } extractRouter(args[0], args[1]);