@choi2021/next-route-visualizer
Version:
πΊοΈ Visualize Next.js routes as an interactive diagram with advanced function analysis. Supports dynamic route detection, function-based navigation patterns, and comprehensive Next.js route mapping. Generate JSON data and beautiful HTML reports to explore
130 lines (129 loc) β’ 7.13 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.visualizeRoutes = visualizeRoutes;
const path_1 = __importDefault(require("path"));
const promises_1 = __importDefault(require("fs/promises"));
const routeParser_js_1 = require("../parser/routeParser.js");
const child_process_1 = require("child_process");
function visualizeRoutes(targetRoot) {
return __awaiter(this, void 0, void 0, function* () {
console.log(`\nπ― Next.js λΌμ°νΈ μκ°νλ₯Ό μμν©λλ€...`);
console.log(`π νλ‘μ νΈ: ${targetRoot}`);
console.log(`β° λΆμ μ€... μ μλ§ κΈ°λ€λ €μ£ΌμΈμ.\n`);
try {
// 1. λΌμ°νΈ νμ± (API μ μΈ, νμ΄μ§+μ»΄ν¬λνΈ ν¬ν¨)
const parser = new routeParser_js_1.RouteParser(targetRoot, {
analyzeNavigation: true,
includeMetadata: true,
});
const result = yield parser.parse();
// 2. API λΌμ°νΈ μ κ±° λ° index νμΌ μμ (νμ²λ¦¬)
const filteredNodes = result.nodes.filter((node) => {
if (node.type === "api" || node.path.startsWith("/api/")) {
console.log(`β API λΌμ°νΈ μ μΈ: ${node.path}`);
return false;
}
return true;
});
// index νμΌλ€μ λ£¨νΈ κ²½λ‘λ‘ λ³ν
const fixedNodes = filteredNodes.map((node) => {
if (node.path === "/index" || node.id === "/index") {
console.log(`π index νμΌμ 루νΈλ‘ λ³ν: ${node.filePath} -> /`);
return Object.assign(Object.assign({}, node), { id: "/", path: "/", label: "/", metadata: Object.assign(Object.assign({}, node.metadata), { depth: 0, segments: [""] }) });
}
return node;
});
// μ€λ³΅ λ
Έλ μ κ±° (κ°μ pathλ₯Ό κ°μ§ λ
Έλλ€ μ€ κ°μ₯ ν° νμΌ μ ν)
const nodeMap = new Map();
fixedNodes.forEach((node) => {
const existing = nodeMap.get(node.path);
if (!existing || node.fileSize > existing.fileSize) {
nodeMap.set(node.path, node);
}
});
const uniqueNodes = Array.from(nodeMap.values());
// API κ΄λ ¨ μ£μ§ μ κ±° λ° /index -> / λ³ν
const filteredEdges = result.edges
.filter((edge) => {
if (edge.source.startsWith("/api/") ||
edge.target.startsWith("/api/")) {
return false;
}
return true;
})
.map((edge) => {
const newEdge = Object.assign({}, edge);
if (edge.source === "/index")
newEdge.source = "/";
if (edge.target === "/index")
newEdge.target = "/";
return newEdge;
});
// 3. ν΅κ³ μ¬κ³μ°
const pageNodes = uniqueNodes.filter((n) => n.type === "page");
const dynamicNodes = uniqueNodes.filter((n) => n.type === "dynamic");
const finalResult = {
nodes: uniqueNodes.sort((a, b) => a.path.localeCompare(b.path)),
edges: filteredEdges,
summary: {
totalPages: pageNodes.length,
totalDynamicRoutes: dynamicNodes.length,
maxDepth: Math.max(...uniqueNodes.map((n) => n.metadata.depth)),
totalNavigationLinks: filteredEdges.length,
},
};
console.log("\nπ λΆμ μλ£!");
console.log("================");
console.log(`π νμ΄μ§: ${finalResult.summary.totalPages}κ°`);
console.log(`π λ€μ΄λ΄λ―Ή λΌμ°νΈ: ${finalResult.summary.totalDynamicRoutes}κ°`);
console.log(`π λ€λΉκ²μ΄μ
λ§ν¬: ${finalResult.summary.totalNavigationLinks}κ°`);
// 4. route-graph.json μ μ₯
const graphPath = path_1.default.resolve(targetRoot, "route-graph.json");
yield promises_1.default.writeFile(graphPath, JSON.stringify(finalResult, null, 2));
console.log(`\nπΎ λΌμ°νΈ λ°μ΄ν°: ${path_1.default.relative(process.cwd(), graphPath)}`);
// 5. μκ°ν HTML μμ±
const templatePath = path_1.default.resolve(__dirname, "../../visualize.html");
const outPath = path_1.default.resolve(targetRoot, "route-visualizer.html");
try {
let html = yield promises_1.default.readFile(templatePath, "utf8");
// λ°μ΄ν° μ½μ
μ μν μμ ν JSON λ³ν
const safeJson = JSON.stringify(finalResult, null, 2)
.replace(/</g, "\\u003c")
.replace(/>/g, "\\u003e");
// μμ λ°μ΄ν° λ‘λ ν¨μλ₯Ό μ€μ λ°μ΄ν°λ‘ κ΅μ²΄
html = html.replace(/fetch\("\.\/example-next\/route-graph\.json"\)/g, `Promise.resolve(${safeJson})`);
yield promises_1.default.writeFile(outPath, html, "utf8");
console.log(`π¨ μκ°ν νμΌ: ${path_1.default.relative(process.cwd(), outPath)}`);
// 6. λΈλΌμ°μ μλ μ€ν
const openCmd = process.platform === "darwin"
? "open"
: process.platform === "win32"
? "start"
: "xdg-open";
(0, child_process_1.exec)(`${openCmd} "${outPath}"`);
console.log(`\nπ λΈλΌμ°μ μμ μκ°νλ₯Ό νμΈνμΈμ!`);
console.log(`π‘ Tip: νμ΄μ§κ° λ€λΉκ²μ΄μ
κ΄κ³λ₯Ό μΈν°λν°λΈνκ² νμν μ μμ΅λλ€.`);
}
catch (_a) {
console.warn(`β οΈ μκ°ν ν
νλ¦Ώμ μ°Ύμ μ μμ΅λλ€: ${templatePath}`);
console.log(`π λμ route-graph.jsonμ νμΈνμΈμ: ${path_1.default.relative(process.cwd(), graphPath)}`);
}
}
catch (error) {
console.error("β μ€λ₯:", error.message);
throw error;
}
});
}