UNPKG

rr-next-routes

Version:

generate nextjs-style routes in your react-router-v7 application

303 lines (298 loc) 11.2 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/react-router/index.ts var react_router_exports = {}; __export(react_router_exports, { appRouterStyle: () => appRouterStyle, generateRouteConfig: () => generateRouteConfig, nextRoutes: () => nextRoutes, pageRouterStyle: () => pageRouterStyle }); module.exports = __toCommonJS(react_router_exports); // src/common/next-routes-common.ts var import_node_fs = require("fs"); var import_node_path = require("path"); // src/common/utils.ts function transformRoutePath(path) { let transformedPath = path.replace(/\[\[\s*([^\]]+)\s*]]/g, ":$1?").replace(/\[\.\.\.\s*([^\]]+)\s*]/g, "*").replace(/\[([^\]]+)]/g, ":$1").replace(/\/\([^)]*\)\//g, "/").replace(/\{([^}]+)\}/g, "$1").replace(/\/\([^)]*\)/g, ""); if (transformedPath === "") { transformedPath = "/" + transformedPath; } return transformedPath; } function isHoistedFolder(name) { return /^{[^{}]+}$/.test(name); } function parseDynamicRoute(name) { const paramMatch = name.match(/\[(.+?)]/); if (!paramMatch) return { routeName: name }; const paramName = paramMatch[1]; return { paramName, routeName: `:${paramName}` }; } function parseCatchAllParam(name) { const paramMatch = name.match(/\[\.\.\.(.+?)]/); if (!paramMatch) return { routeName: name }; const paramName = paramMatch[1]; return { paramName, routeName: "*" // Placeholder to indicate "catch-all" route for now }; } function parseOptionalDynamicRoute(name) { const paramMatch = name.match(/\[\[(.+?)]]/); if (!paramMatch) return { routeName: name }; const paramName = paramMatch[1]; return { paramName, routeName: `:${paramName}?` }; } function parseParameter(name) { if (name.startsWith("[[") && name.endsWith("]]")) { return parseOptionalDynamicRoute(name); } else if (name.startsWith("[...") && name.endsWith("]")) { return parseCatchAllParam(name); } else if (name.startsWith("[") && name.endsWith("]")) { return parseDynamicRoute(name); } else { return { routeName: name }; } } function deepSortByPath(value) { if (Array.isArray(value)) { return value.map(deepSortByPath).sort((a, b) => compareByPath(a, b)); } if (typeof value === "object" && value !== null) { if ("path" in value) { return { ...value, children: value.children ? deepSortByPath(value.children) : void 0 }; } return Object.keys(value).sort().reduce((acc, key) => { acc[key] = deepSortByPath(value[key]); return acc; }, {}); } return value; } function compareByPath(a, b) { const pathA = a.path || ""; const pathB = b.path || ""; const aHoisted = a.file?.includes("/{"); const bHoisted = b.file?.includes("/{"); if (aHoisted && !bHoisted) return -1; if (!aHoisted && bHoisted) return 1; return pathA.localeCompare(pathB); } function printRoutesAsTable(routes) { function extractRoutesForTable(routes2, parentLayout = null) { const result = []; const sortedRoutes = routes2.sort((a, b) => { const pathA = a.path ?? ""; const pathB = b.path ?? ""; return pathA.localeCompare(pathB); }); const pathsFirst = sortedRoutes.filter((route2) => route2.path); const layoutsLast = sortedRoutes.filter((route2) => !route2.path && route2.children); pathsFirst.forEach((route2) => { result.push({ routePath: route2.path, routeFile: route2.file, parentLayout: parentLayout ?? void 0 }); }); layoutsLast.forEach((layout2) => { result.push({ routePath: "(layout)", routeFile: layout2.file, parentLayout: parentLayout ?? void 0 }); if (layout2.children) { const layoutChildren = extractRoutesForTable(layout2.children, layout2.file); result.push(...layoutChildren); } }); return result; } console.groupCollapsed("\u2705 Generated Routes Table (open to see generated routes)"); console.table(extractRoutesForTable(routes)); console.groupEnd(); } function printRoutesAsTree(routes, indent = 0) { function printRouteTree(routes2, indent2 = 0) { const indentation = " ".repeat(indent2); const sortedRoutes = routes2.sort((a, b) => { const pathA = a.path ?? ""; const pathB = b.path ?? ""; return pathA.localeCompare(pathB); }); const pathsFirst = sortedRoutes.filter((route2) => route2.path); const layoutsLast = sortedRoutes.filter((route2) => !route2.path && route2.children); pathsFirst.forEach((route2) => { const routePath = `"${route2.path}"`; console.log(`${indentation}\u251C\u2500\u2500 ${routePath} (${route2.file})`); }); layoutsLast.forEach((route2) => { console.log(`${indentation}\u251C\u2500\u2500 (layout) (${route2.file})`); if (route2.children) { printRouteTree(route2.children, indent2 + 1); } }); } console.groupCollapsed("\u2705 Generated Route Tree (open to see generated routes)"); printRouteTree(routes, indent); console.groupEnd(); } // src/common/next-routes-common.ts var appRouterStyle = { folderName: "", print: "info", layoutFileName: "layout", routeFileNames: ["page", "route"], // in nextjs this is the difference between a page (with components) and an api route (without components). in react-router an api route (resource route) just does not export a default component. extensions: [".tsx", ".ts", ".jsx", ".js"], routeFileNameOnly: true, // all files with names different from routeFileNames get no routes enableHoistedFolders: false }; var pageRouterStyle = { folderName: "pages", print: "info", layoutFileName: "_layout", //layouts do no exist like that in nextjs pages router so we use a special file. routeFileNames: ["index"], extensions: [".tsx", ".ts", ".jsx", ".js"], routeFileNameOnly: false, // all files without a leading underscore get routes as long as the extension matches enableHoistedFolders: false }; var defaultOptions = appRouterStyle; function createRouteConfig(name, parentPath, relativePath, folderName, routeFileNames, indexCreator, routeCreator) { if (routeFileNames.includes(name) && folderName.startsWith("[") && folderName.endsWith("]")) { const { routeName: routeName2 } = parseParameter(folderName); const routePath2 = parentPath === "" ? routeName2 : `${parentPath.replace(folderName, "")}${routeName2}`; return routeCreator(transformRoutePath(routePath2), relativePath); } if (routeFileNames.includes(name)) { if (parentPath === "") { return indexCreator(relativePath); } else { return routeCreator(transformRoutePath(parentPath), relativePath); } } const { routeName } = parseParameter(name); const routePath = parentPath === "" ? `/${routeName}` : `${parentPath}/${routeName}`; return routeCreator(transformRoutePath(routePath), relativePath); } function generateNextRoutes(options = defaultOptions, getAppDir, indexCreator, routeCreator, layoutCreator) { const { folderName: baseFolder = defaultOptions.folderName, print: printOption = defaultOptions.print, extensions = defaultOptions.extensions, layoutFileName = defaultOptions.layoutFileName, routeFileNames = defaultOptions.routeFileNames, routeFileNameOnly = defaultOptions.routeFileNameOnly, enableHoistedFolders = defaultOptions.enableHoistedFolders } = options; let appDirectory = getAppDir(); const pagesDir = (0, import_node_path.resolve)(appDirectory, baseFolder); function scanDir(dirPath) { return { folderName: (0, import_node_path.parse)(dirPath).base, files: (0, import_node_fs.readdirSync)(dirPath).sort((a, b) => { const { ext: aExt, name: aName } = (0, import_node_path.parse)(a); return routeFileNames.includes(aName) && extensions.includes(aExt) ? -1 : 1; }) }; } function scanDirectory(dir, parentPath = "") { const routes = []; const { files, folderName } = scanDir(dir); const layoutFile = files.find((item) => { const { ext, name } = (0, import_node_path.parse)(item); return name === layoutFileName && extensions.includes(ext); }); const currentLevelRoutes = []; files.forEach((item) => { if (item.startsWith("_")) return; const fullPath = (0, import_node_path.join)(dir, item); const stats = (0, import_node_fs.statSync)(fullPath); const { name, ext, base } = (0, import_node_path.parse)(item); const relativePath = (0, import_node_path.join)(baseFolder, (0, import_node_path.relative)(pagesDir, fullPath)); if (layoutFileName && name === layoutFileName) return; if (stats.isDirectory()) { const nestedRoutes = scanDirectory(fullPath, `${parentPath}/${base}`); (layoutFile && !(enableHoistedFolders && isHoistedFolder(name)) ? currentLevelRoutes : routes).push(...nestedRoutes); } else if (extensions.includes(ext)) { if (routeFileNameOnly && !routeFileNames.includes(name)) return; const routeConfig = createRouteConfig(name, parentPath, relativePath, folderName, routeFileNames, indexCreator, routeCreator); (layoutFile ? currentLevelRoutes : routes).push(routeConfig); } }); if (layoutFile) { const layoutPath = (0, import_node_path.join)(baseFolder, (0, import_node_path.relative)(pagesDir, (0, import_node_path.join)(dir, layoutFile))); routes.push(layoutCreator(layoutPath, currentLevelRoutes)); } else { routes.push(...currentLevelRoutes); } return routes; } const results = scanDirectory(pagesDir); switch (printOption) { case "tree": printRoutesAsTree(results); break; case "table": printRoutesAsTable(results); break; case "info": console.log("\u2705 Generated Routes"); break; case "no": break; } return deepSortByPath(results); } // src/react-router/index.ts var import_routes = require("@react-router/dev/routes"); function generateRouteConfig(options = appRouterStyle) { return nextRoutes(options); } function nextRoutes(options = appRouterStyle) { return generateNextRoutes( options, import_routes.getAppDirectory, import_routes.index, import_routes.route, import_routes.layout ); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { appRouterStyle, generateRouteConfig, nextRoutes, pageRouterStyle }); //# sourceMappingURL=index.cjs.map