Created
May 5, 2021 12:22
-
-
Save etienne-dldc/89873615b4b2367122f8ced801663fb6 to your computer and use it in GitHub Desktop.
Extract routes in NextJS project to a TS file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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