@orca-fe/vite-plugin-react-convention-routes
Version:
A vite plugin for react convention routes
177 lines (168 loc) • 5.51 kB
JavaScript
// 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
};