rr-next-routes
Version:
generate nextjs-style routes in your react-router-v7 application
303 lines (298 loc) • 11.2 kB
JavaScript
;
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