UNPKG

@router-cli/react-router-dev

Version:

File based routing cli for react-router-dom.

203 lines (202 loc) 9 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import * as fs from 'fs'; import glob from "fast-glob"; import * as loggingUtils from "../utils/logging"; import { trimCharEnd } from "./utils/strings"; import { transformRoute } from "./utils/code-scanning"; import { CODE_NAMING, populateTemplate } from "./code-gen"; import { trimExt } from "./code-gen/utils"; import { exec } from 'child_process'; import path from "path"; export class Generator { constructor(config, verbose) { this.verboseLog = (section, log) => { if (this._verbose) { loggingUtils.verboseLog(section, log); } }; this._currentOutput = fs.existsSync(config.output) ? fs.readFileSync(config.output, 'utf-8') : ""; this._verbose = verbose; this._config = config; this._config.source = trimCharEnd(this._config.source, "/"); this._config.sourceAlias = this._config.sourceAlias ? trimCharEnd(this._config.source, "/") : undefined; } generate() { return __awaiter(this, void 0, void 0, function* () { const routes = yield this.discoverRouteFiles(); const codeOutput = this.generateCode(routes); yield this.writeFile(codeOutput); }); } generateCode(routes) { const layouts = { imports: [], routes: [], }; const pages = { imports: [], routes: [], }; const imports = []; const exports = []; const appRouteComponents = { app: false, notFound: false, error: false, pending: false, }; for (const item of routes.pages.values()) { pages.imports.push(`"${item.typedRoute}": () => import("${item.importSource}").then(x => x.default)`); pages.routes.push(`"${item.typedRoute}": ExtractImportType<typeof ${CODE_NAMING.pages}["${item.typedRoute}"]>`); } for (const item of routes.layouts.values()) { layouts.imports.push(`"${item.typedRoute}": () => import("${item.importSource}").then(x => x.default)`); layouts.routes.push(`"${item.typedRoute}": ExtractImportType<typeof ${CODE_NAMING.layouts}["${item.typedRoute}"]>`); } if (routes.appRoutes.app) { imports.push(`import App from "${routes.appRoutes.app}";`); appRouteComponents.app = true; } if (routes.appRoutes.notFound) { imports.push(`import NotFound from "${routes.appRoutes.notFound}";`); appRouteComponents.notFound = true; } if (routes.appRoutes.error) { imports.push(`import DefaultErrorComponent from "${routes.appRoutes.error}";`); exports.push(`export { DefaultErrorComponent };`); appRouteComponents.error = true; } if (routes.appRoutes.pending) { imports.push(`import DefaultPendingComponent from "${routes.appRoutes.pending}";`); exports.push(`export { DefaultPendingComponent };`); appRouteComponents.pending = true; } let routerType = "createBrowserRouter"; if (this._config.type === "hash") { routerType = "createHashRouter"; } if (this._config.type === "memory") { routerType = "createMemoryRouter"; } return populateTemplate({ layouts, pages, appRouteComponents, imports, exports, routerType }); } discoverRouteFiles() { return __awaiter(this, void 0, void 0, function* () { const pagesPromise = yield glob([ this._config.source + '/**/?($)[\\w-]*.page.tsx' ], { onlyFiles: true }); const layoutsPromise = yield glob([ this._config.source + '/**/_layout.tsx', ], { onlyFiles: true }); const reservedRoutesPromise = yield glob([ this._config.source + '/_app.tsx', this._config.source + '/_404.tsx', this._config.source + '/_error.tsx', this._config.source + '/_pending.tsx', ], { onlyFiles: true }); const [pages, layouts, reservedRoutes] = yield Promise.all([ pagesPromise, layoutsPromise, reservedRoutesPromise ]); pages.sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)); layouts.sort((a, b) => a.split("/").length - b.split("/").length); this.verboseLog(`discovered routes`, () => { console.log("Pages"); pages.forEach(x => console.log(x)); console.log("Layouts"); layouts.forEach(x => console.log(x)); console.log("Reserved"); reservedRoutes.forEach(x => console.log(x)); }); const appRoutes = {}; if (reservedRoutes.length) { const app = reservedRoutes.find(x => x.endsWith("/_app.tsx")); if (app) { const relativeSource = this.getRelativeSource(app); appRoutes.app = this.getImportSource(relativeSource); } const notFound = reservedRoutes.find(x => x.endsWith("/_404.tsx")); if (notFound) { const relativeSource = this.getRelativeSource(notFound); appRoutes.notFound = this.getImportSource(relativeSource); } const error = reservedRoutes.find(x => x.endsWith("/_error.tsx")); if (error) { const relativeSource = this.getRelativeSource(error); appRoutes.error = this.getImportSource(relativeSource); } const pending = reservedRoutes.find(x => x.endsWith("/_pending.tsx")); if (pending) { const relativeSource = this.getRelativeSource(pending); appRoutes.pending = this.getImportSource(relativeSource); } } return { pages: new Map(pages.map(item => { const data = this.processRoute(item, "page"); return [data.fullRoute, data]; })), layouts: new Map(layouts.map(item => { const data = this.processRoute(item, "layout"); return [data.fullRoute, data]; })), appRoutes }; }); } processRoute(srcPath, type) { const relativeSource = this.getRelativeSource(srcPath); const importSource = this.getImportSource(relativeSource); const typedRoute = "/" + (transformRoute(relativeSource, this._config.hiddenDirectories) || "") + (type === "layout" ? "/layout" : ""); const fullRoute = typedRoute.replace("/$", "/:").replace("/$catchAll", "/*"); return { fullRoute, typedRoute, importSource, }; } getImportSource(relativeSrc) { const { sourceAlias, source } = this._config; return `${(sourceAlias || source)}/${trimExt(relativeSrc)}`; } getRelativeSource(srcPath) { return srcPath.slice(this._config.source.length + 1, srcPath.length); } writeFile(output) { if (this._currentOutput !== output && output) { console.log("Writing Routes File."); fs.writeFileSync(this._config.output, output); if (this._config.formatter) { if (this._config.formatter === "prettier") { const prettier = path.resolve('./node_modules/.bin/prettier'); if (fs.existsSync(prettier)) { exec(`${prettier} --write --cache ${this._config.output}`); } } if (this._config.formatter === "eslint") { const eslint = path.resolve('./node_modules/.bin/eslint'); if (fs.existsSync(eslint)) { exec(`${eslint} --format ${this._config.output}`); } } } this._currentOutput = output; } } }