UNPKG

vite-plugin-react-routes

Version:

A vite plugin support setup React Router by JSON config file.

191 lines (176 loc) 6.25 kB
'use strict'; const fs = require('fs'); const path = require('path'); const vite = require('vite'); const esbuild = require('esbuild'); const jiti = require('jiti'); const upperCamelcase = require('uppercamelcase'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; } const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); const path__default = /*#__PURE__*/_interopDefaultLegacy(path); const jiti__default = /*#__PURE__*/_interopDefaultLegacy(jiti); const upperCamelcase__default = /*#__PURE__*/_interopDefaultLegacy(upperCamelcase); function winPath(path2) { const isExtendedLengthPath = /^\\\\\?\\/.test(path2); if (isExtendedLengthPath) { return path2; } return path2.replace(/\\/g, "/"); } function transformRelativePathToAbsPath(rPath, basePath = "./src") { const joinedPath = path__default.join(basePath, rPath); const targetPath = joinedPath.startsWith("/") ? joinedPath : `./${joinedPath}`; const absPath = jiti__default(process.cwd(), { extensions: [".js", ".mjs", ".cjs", ".ts", ".jsx", ".tsx"] }).resolve(`${targetPath}`); return winPath(absPath); } function transformAbsPathToComponentName(absPath) { return upperCamelcase__default(absPath.replace(/^.*?:/, "").slice(1).replace(/\//g, "-")); } function transformAbsPathComponentPairsToImportCode(absPathComponentPairs) { return absPathComponentPairs.map((item) => { const [absPath, componentName] = item; return `import ${componentName} from '${absPath}';`; }).join("\n"); } function trasnformRouteConfigItemToCode(routesConfig, pathComponentNameMap) { const resultCodeItems = []; routesConfig.forEach((routeConfig) => { if (!routeConfig.path) { throw new Error('"path" must be defined in route'); } if (routeConfig.layout && routeConfig.children && routeConfig.children.length) { const codeItem = ` { path: '${routeConfig.path}', children: ${trasnformRouteConfigItemToCode(routeConfig.children.map((item) => ({ layout: routeConfig.layout, ...item })), pathComponentNameMap)}, } `; resultCodeItems.push(codeItem); } else if (routeConfig.component) { const ComponentName = pathComponentNameMap[routeConfig.component]; if (routeConfig.layout) { const LayoutComponentName = pathComponentNameMap[routeConfig.layout]; const codeItem = ` { path: '${routeConfig.path}', element: ( <${LayoutComponentName}> <${ComponentName} /> </${LayoutComponentName}> ), } `; resultCodeItems.push(codeItem); } else { const codeItem = ` { path: '${routeConfig.path}', element: <${ComponentName} />, } `; resultCodeItems.push(codeItem); } } }); return `[${resultCodeItems.join(",\n")}]`; } function flatAllRelativePath(routesConfig) { const pathResult = []; routesConfig.forEach((routeConfig) => { if (routeConfig.layout && routeConfig.children && routeConfig.children.length) { const childrenResult = flatAllRelativePath(routeConfig.children); pathResult.push(routeConfig.layout); pathResult.push(...childrenResult); } else if (routeConfig.component && routeConfig.path) { pathResult.push(routeConfig.component); } }); return Array.from(/* @__PURE__ */ new Set([...pathResult])); } function transformRouteConfigToCode(routesConfig) { const allRelativePaths = flatAllRelativePath(routesConfig); const pathComponentNamePairs = allRelativePaths.map((relativePath) => { const absPath = transformRelativePathToAbsPath(relativePath); return [relativePath, absPath, transformAbsPathToComponentName(absPath)]; }); const relPathComponentNameMap = Object.fromEntries(pathComponentNamePairs.map((item) => [item[0], item[2]])); const routesCode = trasnformRouteConfigItemToCode(routesConfig, relPathComponentNameMap); const absPathComponentPairs = pathComponentNamePairs.map((item) => [item[1], item[2]]); const importCode = transformAbsPathComponentPairsToImportCode(absPathComponentPairs); return { routesCode, importCode }; } function generateCodeByImportAndRouteCode(importCode, routeCode) { return ` import React, { FC } from 'react'; import { HashRouter, BrowserRouter, useRoutes } from 'react-router-dom'; ${importCode} const InnerRoutes: FC = () => { const element = useRoutes(${routeCode}); return element; }; type RouterProps = { mode?: 'hash' | 'browser'; }; const ReactRouter: FC<RouterProps> = ({ mode = 'hash' }) => { if (mode === 'hash') { return ( <HashRouter> <InnerRoutes /> </HashRouter> ); } return ( <BrowserRouter> <InnerRoutes /> </BrowserRouter> ); }; export default ReactRouter; `; } function generateCode(routesConfig) { const { importCode, routesCode } = transformRouteConfigToCode(routesConfig); return generateCodeByImportAndRouteCode(importCode, routesCode); } const virtualModuleId = "virtual:generated-react-router"; const resolvedVirutalModuleId = `\0${virtualModuleId}`; function VitePluginReactRouter(userOptions) { const { routesFile = "./routes.json" } = userOptions || {}; const routesStr = fs__default.readFileSync(path__default.resolve(process.cwd(), routesFile), "utf-8"); const routes = JSON.parse(routesStr); const codeStr = generateCode(routes); let command; return { name: "vite-plugin-react-router", configResolved(config) { command = config.command; }, resolveId(id) { if (id === virtualModuleId) { return resolvedVirutalModuleId; } return null; }, load(id) { if (id === resolvedVirutalModuleId) { return codeStr; } return null; }, async transform(code, id) { if (id === resolvedVirutalModuleId) { const esbuildResult = command === "serve" ? await vite.transformWithEsbuild(code, "/vite-plugin-react-router", { loader: "tsx" }) : await esbuild.transform(code, { loader: "tsx" }); return esbuildResult.code; } return null; } }; } module.exports = VitePluginReactRouter;