UNPKG

rr-next-routes

Version:

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

374 lines (368 loc) 13.2 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; 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/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((route3) => route3.path); const layoutsLast = sortedRoutes.filter((route3) => !route3.path && route3.children); pathsFirst.forEach((route3) => { result.push({ routePath: route3.path, routeFile: route3.file, parentLayout: parentLayout ?? void 0 }); }); layoutsLast.forEach((layout3) => { result.push({ routePath: "(layout)", routeFile: layout3.file, parentLayout: parentLayout ?? void 0 }); if (layout3.children) { const layoutChildren = extractRoutesForTable(layout3.children, layout3.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((route3) => route3.path); const layoutsLast = sortedRoutes.filter((route3) => !route3.path && route3.children); pathsFirst.forEach((route3) => { const routePath = `"${route3.path}"`; console.log(`${indentation}\u251C\u2500\u2500 ${routePath} (${route3.file})`); }); layoutsLast.forEach((route3) => { console.log(`${indentation}\u251C\u2500\u2500 (layout) (${route3.file})`); if (route3.children) { printRouteTree(route3.children, indent2 + 1); } }); } console.groupCollapsed("\u2705 Generated Route Tree (open to see generated routes)"); printRouteTree(routes, indent); console.groupEnd(); } var init_utils = __esm({ "src/common/utils.ts"() { "use strict"; } }); // src/common/next-routes-common.ts import { readdirSync, statSync } from "node:fs"; import { join, parse, relative, resolve } from "node:path"; 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 = resolve(appDirectory, baseFolder); function scanDir(dirPath) { return { folderName: parse(dirPath).base, files: readdirSync(dirPath).sort((a, b) => { const { ext: aExt, name: aName } = 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 } = parse(item); return name === layoutFileName && extensions.includes(ext); }); const currentLevelRoutes = []; files.forEach((item) => { if (item.startsWith("_")) return; const fullPath = join(dir, item); const stats = statSync(fullPath); const { name, ext, base } = parse(item); const relativePath = join(baseFolder, 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 = join(baseFolder, relative(pagesDir, 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); } var appRouterStyle, pageRouterStyle, defaultOptions; var init_next_routes_common = __esm({ "src/common/next-routes-common.ts"() { "use strict"; init_utils(); 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 }; 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 }; defaultOptions = appRouterStyle; } }); // src/react-router/index.ts var react_router_exports = {}; __export(react_router_exports, { appRouterStyle: () => appRouterStyle, generateRouteConfig: () => generateRouteConfig, nextRoutes: () => nextRoutes, pageRouterStyle: () => pageRouterStyle }); import { getAppDirectory, route, layout, index } from "@react-router/dev/routes"; function generateRouteConfig(options = appRouterStyle) { return nextRoutes(options); } function nextRoutes(options = appRouterStyle) { return generateNextRoutes( options, getAppDirectory, index, route, layout ); } var init_react_router = __esm({ "src/react-router/index.ts"() { "use strict"; init_next_routes_common(); } }); // src/remix/index.ts var remix_exports = {}; __export(remix_exports, { appRouterStyle: () => appRouterStyle, generateRouteConfig: () => generateRouteConfig2, nextRoutes: () => nextRoutes2, pageRouterStyle: () => pageRouterStyle }); import { getAppDirectory as getAppDirectory2, index as index2, route as route2, layout as layout2 } from "@remix-run/route-config"; function generateRouteConfig2(options = appRouterStyle) { return nextRoutes2(options); } function nextRoutes2(options = appRouterStyle) { return generateNextRoutes( options, getAppDirectory2, index2, route2, layout2 ); } var init_remix = __esm({ "src/remix/index.ts"() { "use strict"; init_next_routes_common(); } }); // src/common/implementationResolver.ts function resolveImplementation() { try { __require("@react-router/dev/routes"); return init_react_router(), __toCommonJS(react_router_exports); } catch (e) { try { __require("@remix-run/route-config"); return init_remix(), __toCommonJS(remix_exports); } catch (e2) { throw new Error( "Could not import from either @react-router/dev/routes or @remix-run/route-config. Please install one of these packages to use this library." ); } } } // src/next-routes.ts init_next_routes_common(); var implementation = resolveImplementation(); var nextRoutes3 = implementation.nextRoutes; var generateRouteConfig3 = implementation.generateRouteConfig; export { appRouterStyle, generateRouteConfig3 as generateRouteConfig, nextRoutes3 as nextRoutes, pageRouterStyle }; //# sourceMappingURL=next-routes.js.map