Skip to content

Instantly share code, notes, and snippets.

@etienne-dldc
Created May 5, 2021 12:22
Show Gist options
  • Save etienne-dldc/89873615b4b2367122f8ced801663fb6 to your computer and use it in GitHub Desktop.
Save etienne-dldc/89873615b4b2367122f8ced801663fb6 to your computer and use it in GitHub Desktop.
Extract routes in NextJS project to a TS file
import * as glob from "glob";
import * as path from "path";
import * as fse from "fs-extra";
import * as prettier from "prettier";
const PARAM_REG = /^\[(.+)\]$/;
type PageObj = { [key: string]: PageObj };
type PathItem = { type: "param" | "static"; name: string };
main().catch((err) => {
console.error(err);
});
async function main() {
const base = process.cwd();
const pagesDir = path.resolve(base, "src/pages");
const generatedDir = path.resolve(base, "src/generated");
const pagesFile = path.resolve(generatedDir, "pages.ts");
const files = glob.sync("**/*", {
cwd: pagesDir,
nodir: true,
});
const EXTENSION_REG = /\.tsx?$/;
const validFiles = files
.filter((file) => {
if (file === "_app.tsx" || file === "_document.tsx") {
return false;
}
if (!EXTENSION_REG.test(file)) {
return false;
}
return true;
})
.map((page) => page.replace(EXTENSION_REG, ""));
const content = generateObjectCode(validFiles, "PAGES");
const contentFormatted = prettier.format(content, { filepath: pagesFile });
await fse.ensureDir(generatedDir);
await fse.writeFile(pagesFile, contentFormatted, { encoding: "utf-8" });
}
function generateObjectCode(files: Array<string>, variableName: string): string {
const pagesObject: PageObj = {};
files.forEach((page) => {
const parts = page.split("/");
let current = pagesObject;
parts.forEach((part) => {
current[part] = current[part] ?? {};
current = current[part];
});
});
function convertPageObj(obj: PageObj, path: Array<PathItem> = []): string {
return `{${Object.entries(obj)
.map(([key, val]) => {
const prop = snakeToCamel(key);
const isParams = key.match(PARAM_REG);
const isFile = isEmptyObject(val);
if (isParams) {
const paramName = isParams[1];
if (isFile) {
return `"${paramName}": (${paramName}: string) => ${renderPath([
...path,
{ type: "param", name: paramName },
])}`;
}
return `"${paramName}": (${paramName}: string) => (${convertPageObj(val, [
...path,
{ type: "param", name: paramName },
])})`;
}
if (isFile) {
if (key === "index") {
return `"${prop}": ${renderPath(path)}`;
}
return `"${prop}": ${renderPath([...path, { type: "static", name: key }])}`;
}
// otherwise: folder
return `"${prop}": ${convertPageObj(val, [...path, { type: "static", name: key }])}`;
})
.join(",\n")}}`;
}
function renderPath(path: Array<PathItem>): string {
if (path.length === 0) {
return `"/"`;
}
const compacted: Array<PathItem> = [{ type: "static", name: "/" }];
path.forEach((item, index) => {
const isLast = index === path.length - 1;
const prev = compacted[compacted.length - 1];
if (prev.type === "static" && item.type === "static") {
compacted[compacted.length - 1] = { type: "static", name: prev.name + item.name + (isLast ? "" : "/") };
return;
}
if (prev.type === "param" && item.type === "param") {
compacted.push({ type: "static", name: "/" });
compacted.push(item);
return;
}
if (prev.type === "static" && item.type === "param") {
compacted.push(item);
if (!isLast) {
compacted.push({ type: "static", name: "/" });
}
return;
}
throw new Error("What ?");
});
return compacted
.map((item) => {
if (item.type === "param") {
return item.name;
}
return `"${item.name}"`;
})
.join(" + ");
}
const content = [`export const ${variableName} = `, convertPageObj(pagesObject), " as const;"].join("");
return content;
}
function isEmptyObject(obj: any): boolean {
return Object.keys(obj).length === 0;
}
function snakeToCamel(str: string): string {
return str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace("-", "").replace("_", ""));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment