UNPKG

@orca-fe/vite-plugin-react-convention-routes

Version:
177 lines (168 loc) 5.51 kB
// src/index.ts import { resolve } from "path"; import chokidar from "chokidar"; import { createLogger } from "vite"; // src/createRoutes.ts import fg from "fast-glob"; // src/buildRoutes.ts import { basename, join } from "path"; function slash(path) { return path.replace(/\\/g, "/"); } var defaultIsIndex = (path) => { const pathWithoutExt = path.split(".").slice(0, -1).join("."); return basename(pathWithoutExt) === "index"; }; var defaultIsLayout = (path) => { const pathWithoutExt = path.split(".").slice(0, -1).join("."); return basename(pathWithoutExt) === "_layout"; }; function escapeDynamicSubPath(path, fallback) { const s = path.replace(/\[([^[\]]+)(\$)?\]/g, (_, v1, v2) => `:${v1}${v2 ? "?" : ""}`).replace(/^\$/g, ":"); if (path === "$") { return "*"; } if (s.includes(":")) { return s; } return fallback ? fallback(s) : s; } var defaultFilter = () => true; var defaultTransformer = (path) => path; function buildRoutes(pageRoot, fileList, options = {}) { const { isLayout = defaultIsLayout, isIndex = defaultIsIndex, filter = defaultFilter, transform = defaultTransformer } = options; const root = [ { path: "" } ]; const cache = /* @__PURE__ */ new Map(); cache.set("", root[0]); function getRoute(path) { var _a; if (cache.has(path)) { return cache.get(path); } const pathArr = path.split("/"); const parentPath = pathArr.slice(0, pathArr.length - 1).join("/"); const currentPath = pathArr[pathArr.length - 1]; const parent = getRoute(parentPath); if (!parent) { return void 0; } const route = { path: escapeDynamicSubPath(currentPath, transform) }; cache.set(path, route); parent.children = (_a = parent.children) != null ? _a : []; parent.children.push(route); return route; } fileList.filter(filter).forEach((filePath) => { var _a; const path = slash(filePath); const pathWithoutExt = path.split(".").slice(0, -1).join("."); const routeIsIndex = isIndex(path); const routeIsLayout = isLayout(path); const parentPath = path.split("/").slice(0, -1).join("/"); const parent = getRoute(parentPath); if (!parent) { console.error("Build route failed: ", path); return; } if (routeIsLayout) { parent.element = `@import|${join(pageRoot, path)}|layout`; return; } const currentPath = basename(pathWithoutExt); parent.children = (_a = parent.children) != null ? _a : []; if (currentPath === "404") { parent.children.push({ path: "*", element: `@import|${join(pageRoot, path)}|` }); return; } const route = { path: routeIsIndex ? void 0 : escapeDynamicSubPath(currentPath, transform), index: routeIsIndex, element: `@import|${join(pageRoot, path)}|` }; parent.children.push(route); cache.set(pathWithoutExt, route); }); return root; } // src/createRoutes.ts var { glob } = fg; var defaultIgnore = ["**/node_modules/**", "**/components/**", "**/layouts/**", "**/services/**", "**/model/**", "**/hox/**"]; var defaultGlobRule = ["*.{js,jsx,ts,tsx}", "**/*.{js,jsx,ts,tsx}"]; async function createRoutes(pageRoot, options = {}) { const { dynamic = true, dot, globRule = defaultGlobRule, deep, ignore = defaultIgnore, ...otherOptions } = options; const pageMapping = await glob(globRule, { cwd: pageRoot, dot, deep, ignore, onlyFiles: true }); let routesString = JSON.stringify(buildRoutes(pageRoot, pageMapping, otherOptions), null, 2); const importList = []; routesString = routesString.replace(/"@import\|(.+)\|(layout)?"/g, (_, path, isLayout) => { if (dynamic) { return `createElement(lazy(() => import('${path}')), {}, ${isLayout ? "createElement(Outlet)" : "undefined"})`; } const escapedPath = path.replace(/[^a-zA-Z0-9_]/g, "_"); importList.push(`import ${escapedPath} from '${path}';`); return `createElement(${escapedPath}, {}, ${isLayout ? "createElement(Outlet)" : "undefined"})`; }); return `/* Generated by \`@orca-fe/vite-plugin-react-convention-routes\` */ import { lazy, createElement } from 'react'; import { Outlet } from 'react-router-dom'; ${importList.join("\n")} const routes = ${routesString}; console.log(routes); export default routes; `; } // src/index.ts var moduleId = "virtual:convention-routes"; var log = createLogger(); function reactConventionRoutes(options = {}) { const defaultPageRoot = resolve(process.cwd(), "src/pages"); const { pageRoot = defaultPageRoot, ...otherProps } = options; return { name: "@orca-fe/vite-plugin-react-convention-routes", enforce: "pre", resolveId(id) { if (id === moduleId) { return `\0${moduleId}`; } return void 0; }, async load(id) { if (id === `\0${moduleId}`) { const template = createRoutes(pageRoot, otherProps); return template; } return void 0; }, configureServer(server) { const watcher = chokidar.watch(pageRoot, { ignoreInitial: true }); const listener = (file) => { log.info(`File ${file} changed, rebuilding routes...`); const vm = server.moduleGraph.getModuleById(`\0${moduleId}`); if (vm) server.moduleGraph.invalidateModule(vm); }; watcher.on("add", listener); watcher.on("unlink", listener); } }; } export { buildRoutes as buildDirTree, reactConventionRoutes as default };