'use strict'; var esModuleLexer = require('es-module-lexer'); var path2 = require('path'); var localPkg = require('local-pkg'); var semver = require('semver'); var minimatch = require('minimatch'); var fs = require('fs'); var changeCase = require('change-case'); var serializeJavascript = require('serialize-javascript'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var path2__namespace = /*#__PURE__*/_interopNamespace(path2); var semver__default = /*#__PURE__*/_interopDefault(semver); var minimatch__default = /*#__PURE__*/_interopDefault(minimatch); var fs__namespace = /*#__PURE__*/_interopNamespace(fs); var serializeJavascript__default = /*#__PURE__*/_interopDefault(serializeJavascript); // src/node/index.ts function detectLegacyMode() { const pkg = localPkg.getPackageInfoSync("react-router-dom"); if (pkg == null ? undefined : pkg.version) { if (semver__default.default.lt(pkg.version, "6.0.0")) { throw new Error("react-router-dom version at least 6.0.0 is required"); } return semver__default.default.lte(pkg.version, "6.3.0"); } return false; } function detectReactRefresh() { const react = localPkg.getPackageInfoSync("@vitejs/plugin-react"); if ((react == null ? undefined : react.version) && semver__default.default.gte(react.version, "4.3.2")) { return true; } const reactSWC = localPkg.getPackageInfoSync("@vitejs/plugin-react-swc"); if ((reactSWC == null ? undefined : reactSWC.version) && semver__default.default.gte(reactSWC.version, "3.6.0")) { return true; } return false; } function normalizeSlashes(file) { return file.split(path2__namespace.default.win32.sep).join("/"); } // src/node/react-router/react-router-remix-routes-option-adapter/defineRoutes.ts var defineRoutes = (callback) => { const routes = /* @__PURE__ */ Object.create(null); const parentRoutes = []; let alreadyReturned = false; const defineRoute = (path9, file, optionsOrChildren, children) => { if (alreadyReturned) { throw new Error( "You tried to define routes asynchronously but started defining routes before the async work was done. Please await all async data before calling `defineRoutes()`" ); } let options; if (typeof optionsOrChildren === "function") { options = {}; children = optionsOrChildren; } else { options = optionsOrChildren || {}; } const route = { path: path9 ? path9 : undefined, index: options.index ? true : undefined, caseSensitive: options.caseSensitive ? true : undefined, id: options.id || createRouteId(file), parentId: parentRoutes.length > 0 ? parentRoutes[parentRoutes.length - 1].id : "root", file }; if (route.id in routes) { throw new Error(`Unable to define routes with duplicate route id: "${route.id}"`); } routes[route.id] = route; if (children) { parentRoutes.push(route); children(); parentRoutes.pop(); } }; callback(defineRoute); alreadyReturned = true; return routes; }; function createRouteId(file) { return normalizeSlashes(stripFileExtension(file)); } function stripFileExtension(file) { return file.replace(/\.[a-z0-9]+$/i, ""); } var defaultOptions = { appDir: "app", routeDir: "routes", basePath: "/", paramPrefixChar: "$", nestedDirectoryChar: "+", routeRegex: /((\${nestedDirectoryChar}[\/\\][^\/\\:?*]+)|[\/\\]((index|route|layout|page)|(_[^\/\\:?*]+)|([^\/\\:?*]+\.route)))\.(ts|tsx|js|jsx|md|mdx)$$/ }; var defaultDefineRoutes = undefined; function flatRoutes(routeDir, defineRoutes2, options = {}) { const routes = _flatRoutes(options.appDir ?? defaultOptions.appDir, options.ignoredRouteFiles ?? [], { ...defaultOptions, ...options, routeDir, defineRoutes: defineRoutes2 }); Object.values(routes).forEach((route) => { if (route.parentId === undefined) { route.parentId = "root"; } }); return routes; } function _flatRoutes(appDir, ignoredFilePatternsOrOptions, options) { let ignoredFilePatterns = []; if (ignoredFilePatternsOrOptions && !Array.isArray(ignoredFilePatternsOrOptions)) { options = ignoredFilePatternsOrOptions; } else { ignoredFilePatterns = ignoredFilePatternsOrOptions ?? []; } if (!options) { options = defaultOptions; } const routeMap = /* @__PURE__ */ new Map(); const nameMap = /* @__PURE__ */ new Map(); const routeDirs = Array.isArray(options.routeDir) ? options.routeDir : [options.routeDir ?? "routes"]; const defineRoutes2 = options.defineRoutes ?? defaultDefineRoutes; if (!defineRoutes2) { throw new Error("You must provide a defineRoutes function"); } const visitFiles = options.visitFiles ?? defaultVisitFiles; const routeRegex = getRouteRegex( options.routeRegex ?? defaultOptions.routeRegex, options.nestedDirectoryChar ?? defaultOptions.nestedDirectoryChar ); for (const routeDir of routeDirs) { visitFiles(path2__namespace.join(appDir, routeDir), (file) => { if (ignoredFilePatterns && ignoredFilePatterns.some((pattern) => minimatch__default.default(file, pattern, { dot: true }))) { return; } if (isRouteModuleFile(file, routeRegex)) { const matchRoute = getRouteInfo(routeDir, file, options); routeMap.set(matchRoute.id, matchRoute); nameMap.set(matchRoute.name, matchRoute); return; } }); } Array.from(routeMap.values()).forEach((matchRoute) => { const parentId = findParentRouteId(matchRoute, nameMap); matchRoute.parentId = parentId; }); function defineNestedRoutes(defineRoute, parentId) { var _a; const childRoutes = Array.from(routeMap.values()).filter((matchRoute) => matchRoute.parentId === parentId); const parentRoute = parentId ? routeMap.get(parentId) : undefined; const parentRoutePath = (parentRoute == null ? undefined : parentRoute.path) ?? "/"; for (const childRoute of childRoutes) { let routePath = ((_a = childRoute == null ? undefined : childRoute.path) == null ? undefined : _a.slice(parentRoutePath.length)) ?? ""; if (routePath.startsWith("/")) { routePath = routePath.slice(1); } const index = childRoute.index; if (index) { const invalidChildRoutes = Object.values(routeMap).filter((matchRoute) => matchRoute.parentId === childRoute.id); if (invalidChildRoutes.length > 0) { throw new Error( `Child routes are not allowed in index routes. Please remove child routes of ${childRoute.id}` ); } defineRoute(routePath, routeMap.get(childRoute.id).file, { index: true }); } else { defineRoute(routePath, routeMap.get(childRoute.id).file, () => { defineNestedRoutes(defineRoute, childRoute.id); }); } } } const routes = defineRoutes2(defineNestedRoutes); return routes; } var routeModuleExts = [".js", ".jsx", ".ts", ".tsx", ".md", ".mdx"]; var serverRegex = /\.server\.(ts|tsx|js|jsx|md|mdx)$/; function isRouteModuleFile(filename, routeRegex) { const isFlatFile = !filename.includes(path2__namespace.sep); if (isFlatFile) { return routeModuleExts.includes(path2__namespace.extname(filename)); } const isRoute = routeRegex.test(filename); if (isRoute) { const isServer = serverRegex.test(filename); return !isServer; } return false; } var memoizedRegex = /* @__PURE__ */ (() => { const cache = {}; return (input) => { if (input in cache) { return cache[input]; } const newRegex = new RegExp(input); cache[input] = newRegex; return newRegex; }; })(); function isIndexRoute(routeId, options) { const nestedDirectoryChar = options.nestedDirectoryChar.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); const indexRouteRegex = memoizedRegex( `((^|[.]|[${nestedDirectoryChar}]\\/)(index|_index))(\\/[^\\/]+)?$|(\\/_?index\\/)` ); return indexRouteRegex.test(routeId); } function getRouteInfo(routeDir, file, options) { const filePath = normalizeSlashes(path2__namespace.join(routeDir, file)); const routeId = createRouteId(filePath); const routeIdWithoutRoutes = routeId.slice(routeDir.length + 1); const index = isIndexRoute(routeIdWithoutRoutes, options); const routeSegments = getRouteSegments( routeIdWithoutRoutes, index, options.paramPrefixChar, options.nestedDirectoryChar ); const routePath = createRoutePath(routeSegments, index, options); const matchRoute = { id: routeId, path: routePath, file: filePath, name: routeSegments.join("/"), segments: routeSegments, index }; return matchRoute; } function createRoutePath(routeSegments, index, options) { let result = ""; const basePath = options.basePath ?? "/"; const paramPrefixChar = options.paramPrefixChar ?? "$"; if (index) { routeSegments[routeSegments.length - 1] = ""; } for (let i = 0; i < routeSegments.length; i++) { let segment = routeSegments[i]; if (segment.startsWith("_")) { continue; } if (segment.endsWith("_")) { segment = segment.slice(0, -1); } if (segment.includes("[") && segment.includes("]")) { let output = ""; let depth = 0; for (const char of segment) { if (char === "[" && depth === 0) { depth++; } else if (char === "]" && depth > 0) { depth--; } else { output += char; } } segment = output; } if (segment.startsWith(paramPrefixChar)) { if (segment === paramPrefixChar) { result += `/*`; } else { result += `/:${segment.slice(1)}`; } } else if (segment.startsWith(`(${paramPrefixChar}`)) { result += `/:${segment.slice(2, segment.length - 1)}?`; } else if (segment.startsWith("(")) { result += `/${segment.slice(1, segment.length - 1)}?`; } else { result += `/${segment}`; } } if (basePath !== "/") { result = basePath + result; } if (result.endsWith("/")) { result = result.slice(0, -1); } return result || undefined; } function findParentRouteId(matchRoute, nameMap) { let parentName = matchRoute.segments.slice(0, -1).join("/"); while (parentName) { if (nameMap.has(parentName)) { return nameMap.get(parentName).id; } parentName = parentName.substring(0, parentName.lastIndexOf("/")); } return undefined; } function getRouteSegments(name, index, paramPrefixChar = "$", nestedDirectoryChar = "+") { var _a; let routeSegments = []; let i = 0; let routeSegment = ""; let state = "START"; let subState = "NORMAL"; let hasPlus = false; const escapedNestedDirectoryChar = nestedDirectoryChar.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); const combinedRegex = new RegExp(`${escapedNestedDirectoryChar}[/\\\\]`, "g"); const testRegex = new RegExp(`${escapedNestedDirectoryChar}[/\\\\]`); const replacePattern = `${escapedNestedDirectoryChar}/_\\.`; const replaceRegex = new RegExp(replacePattern); if (replaceRegex.test(name)) { const replaceRegexGlobal = new RegExp(replacePattern, "g"); name = name.replace(replaceRegexGlobal, `_${nestedDirectoryChar}/`); } if (testRegex.test(name)) { name = name.replace(combinedRegex, "."); hasPlus = true; } const hasFolder = /\//.test(name); if ((hasPlus && hasFolder || !hasPlus) && !name.endsWith(".route")) { const last = name.lastIndexOf("/"); if (last >= 0) { name = name.substring(0, last); } } const pushRouteSegment = (routeSegment2) => { if (routeSegment2) { routeSegments.push(routeSegment2); } }; while (i < name.length) { const char = name[i]; switch (state) { case "START": if (routeSegment.includes(paramPrefixChar) && !(routeSegment.startsWith(paramPrefixChar) || routeSegment.startsWith(`(${paramPrefixChar}`))) { throw new Error(`Route params must start with prefix char ${paramPrefixChar}: ${routeSegment}`); } if (routeSegment.includes("(") && !routeSegment.startsWith("(") && !routeSegment.endsWith(")")) { throw new Error(`Optional routes must start and end with parentheses: ${routeSegment}`); } pushRouteSegment(routeSegment); routeSegment = ""; state = "PATH"; continue; // restart without advancing index case "PATH": if (isPathSeparator(char) && subState === "NORMAL") { state = "START"; break; } else if (char === "[") { subState = "ESCAPE"; } else if (char === "]") { subState = "NORMAL"; } routeSegment += char; break; } i++; } pushRouteSegment(routeSegment); if (routeSegments.at(-1) === "route") { routeSegments = routeSegments.slice(0, -1); } if (!index && hasPlus && ((_a = routeSegments.at(-1)) == null ? undefined : _a.startsWith("_"))) { routeSegments = routeSegments.slice(0, -1); } return routeSegments; } var pathSeparatorRegex = /[\/\\.]/; function isPathSeparator(char) { return pathSeparatorRegex.test(char); } function defaultVisitFiles(dir, visitor, baseDir = dir) { for (const filename of fs__namespace.readdirSync(dir)) { const file = path2__namespace.resolve(dir, filename); const stat = fs__namespace.statSync(file); if (stat.isDirectory()) { defaultVisitFiles(file, visitor, baseDir); } else if (stat.isFile()) { visitor(path2__namespace.relative(baseDir, file)); } } } var getRouteRegex = (RegexRequiresNestedDirReplacement, nestedDirectoryChar) => { nestedDirectoryChar = nestedDirectoryChar.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); return new RegExp( RegexRequiresNestedDirReplacement.source.replace("\\${nestedDirectoryChar}", `[${nestedDirectoryChar}]`) ); }; async function resolveConfig(ctx) { const { remixOptions: { appDirectory, flatRoutesOptions, routes } } = ctx; const rootRouteFile = findEntry(appDirectory, "root"); if (!rootRouteFile) { throw new Error(`Missing "root" route file in ${appDirectory}`); } const routeManifest = { root: { path: "", id: "root", file: rootRouteFile } }; const flatedRotues = flatRoutes(flatRoutesOptions.routeDir, (flatRoutesOptions == null ? undefined : flatRoutesOptions.defineRoutes) || defineRoutes, { ...flatRoutesOptions, ignoredRouteFiles: [...(flatRoutesOptions == null ? undefined : flatRoutesOptions.ignoredRouteFiles) || [], "**/*.lazy.*"] }); for (const route of Object.values(flatedRotues)) { routeManifest[route.id] = { ...route, parentId: route.parentId || "root" }; } if (routes) { const manualRoutes = await routes(defineRoutes, { ignoredRouteFiles: ["**/*.lazy.*"] }); for (const route of Object.values(manualRoutes)) { routeManifest[route.id] = { ...route, parentId: route.parentId || "root" }; } } return { routeManifest }; } var entryExts = [".js", ".jsx", ".ts", ".tsx"]; function findEntry(dir, basename) { for (const ext of entryExts) { const file = path2__namespace.default.resolve(dir, basename + ext); if (fs__namespace.default.existsSync(file)) { return path2__namespace.default.relative(dir, file); } } return undefined; } // src/node/react-router/react-router-dev/vite/import-vite-esm-sync.ts var vite; async function preloadViteEsm() { vite = await import('vite'); } function importViteEsmSync() { return vite; } var resolveFileUrl = ({ rootDirectory }, filePath) => { const vite2 = importViteEsmSync(); const relativePath = path2__namespace.relative(rootDirectory, filePath); const isWithinRoot = !relativePath.startsWith("..") && !path2__namespace.isAbsolute(relativePath); if (!isWithinRoot) { return path2__namespace.posix.join("/@fs", vite2.normalizePath(filePath)); } return `/${vite2.normalizePath(relativePath)}`; }; // src/node/react-router/react-router-dev/vite/plugin.ts var compileRouteFile = async (ctx, routeFile, readRouteFile) => { const { viteChildCompiler, viteConfig } = ctx; if (!viteChildCompiler) { throw new Error("Vite child compiler not found"); } const ssr = (viteConfig == null ? undefined : viteConfig.command) === "build"; const { pluginContainer, moduleGraph } = viteChildCompiler; const routePath = path2__namespace.default.resolve(ctx.remixOptions.appDirectory, routeFile); const url = resolveFileUrl(ctx, routePath); const resolveId = async () => { const result = await pluginContainer.resolveId(url, undefined, { ssr }); if (!result) throw new Error(`Could not resolve module ID for ${url}`); return result.id; }; const [id, code] = await Promise.all([ resolveId(), fs__namespace.default.promises.readFile(routePath, "utf-8"), // pluginContainer.transform(...) fails if we don't do this first: moduleGraph.ensureEntryFromUrl(url, ssr) ]); const transformed = await pluginContainer.transform(code, id, { ssr }); return transformed.code; }; var getRouteModuleExports = async (ctx, routeFile, readRouteFile) => { if (!ctx.viteChildCompiler) { throw new Error("Vite child compiler not found"); } const code = await compileRouteFile(ctx, routeFile); return getExportNames(code); }; var getExportNames = (code) => { const [, exportSpecifiers] = esModuleLexer.parse(code); return exportSpecifiers.map(({ n: name }) => name); }; var getRouteManifestModuleExports = async (ctx) => { const entries = await Promise.all( Object.entries(ctx.routeManifest).map(async ([key, route]) => { const sourceExports = await getRouteModuleExports(ctx, route.file); return [key, sourceExports]; }) ); return Object.fromEntries(entries); }; // node_modules/.pnpm/p-is-promise@4.0.0/node_modules/p-is-promise/index.js var isObject = (value) => value !== null && (typeof value === "object" || typeof value === "function"); function isPromise(value) { return value instanceof Promise || isObject(value) && typeof value.then === "function" && typeof value.catch === "function"; } function validateRouteDir(dir) { if (!fs__namespace.default.existsSync(dir) || !fs__namespace.default.statSync(dir).isDirectory()) { throw new Error(`[vite-plugin-remix-flat-routes] routes directory not found: ${dir}`); } } function capitalize(str) { if (!str) return str; return str.charAt(0).toUpperCase() + str.slice(1); } function getVitePluginName(plugin) { if (!isPromise(plugin) && typeof plugin === "object" && plugin !== null && "name" in plugin) { return plugin.name; } } function getRouteFiles(config) { const vite2 = importViteEsmSync(); return Object.values(config.routeManifest).map( (route) => vite2.normalizePath(path2__namespace.default.join(config.appDirectory, route.file)) ) || []; } var reactRefreshUnsupportedExports = [ "handle", "loader", "clientLoader", "action", "clientAction", "shouldRevalidate", "lazy" ]; function reactRefreshHack(config) { const { viteConfig, appDirectory, routeManifest, enable } = config; if (!enable) return ""; const routeFiles = getRouteFiles({ viteConfig, appDirectory, routeManifest }); return `if (typeof window !== 'undefined' && import.meta.hot) { window.__getReactRefreshIgnoredExports = ({ id, prevExports, nextExports }) => { const routeFiles = ${JSON.stringify(routeFiles)} import.meta.hot.send('remix-flat-routes:react-refresh', { id, prevExports, nextExports, }) if (routeFiles.includes(id)) { return ${JSON.stringify(reactRefreshUnsupportedExports)} } return [] } } `; } function pick(obj, keys) { const result = {}; for (const key of keys) { if (obj[key] !== undefined) { result[key] = obj[key]; } } return result; } function isObjEq(a, b) { a = pick(a, reactRefreshUnsupportedExports); b = pick(b, reactRefreshUnsupportedExports); const keys1 = Object.keys(a); const keys2 = Object.keys(b); if (keys1.length !== keys2.length) { return false; } for (const key of keys1) { if (serializeJavascript__default.default(a[key]) !== serializeJavascript__default.default(b[key])) { return false; } } return true; } // src/node/route-util.ts var RouteUtil = class { constructor(ctx) { this.ctx = ctx; } async stringifyRoutes(routes) { const staticImport = []; const routesString = await this.routesToString(routes, staticImport); return { componentsString: staticImport.join("\n"), routesString }; } async routesToString(routes, staticImport) { let strs; if (this.ctx.isLegacyMode) { strs = await Promise.all(routes.map((route) => this.legacyRouteToString(route, staticImport))); } else { strs = await Promise.all(routes.map((route) => this.dataApiRouteToString(route, staticImport))); } return `[${strs.join(",")}]`; } /** * @description 判断是否在导出中 * @param route 路由对象 * @param exportName 导出名称 * @example route.hasComponent */ isInExports(route, exportName) { return route[`has${capitalize(exportName)}`]; } /** * @description 从导出中转换 React Element */ reactElementInExports(route, { importee, namedExport, field = namedExport }) { return this.isInExports(route, field || namedExport) ? `React.createElement(${importee}.${namedExport})` : ""; } createPropsSetter(props = /* @__PURE__ */ new Map()) { return { props, setProps: (name, value) => { if (value) { props.set(name, `${value}`); } } }; } /** * 传统路由模式下的路由转换 */ async legacyRouteToString(route, staticImport) { var _a; const vite2 = importViteEsmSync(); const componentPath = vite2.normalizePath(path2__namespace.default.resolve(this.ctx.remixOptions.appDirectory, route.file)); const componentName = changeCase.pascalSnakeCase(route.id); const isLazyComponent = route.hasDefaultExport; const { setProps, props } = this.createPropsSetter(); if (isLazyComponent) { setProps( "handle", /*js*/ `async () => { const { handle } = await import('${componentPath}') if (!handle) return return handle }` ); setProps("lazyComponent", `() => import('${componentPath}')`); } else if (route.hasComponent) { staticImport.push(`import * as ${componentName} from '${componentPath}';`); setProps( "element", this.reactElementInExports(route, { importee: componentName, namedExport: "Component" }) ); } setProps("path", route.index && !route.path ? `''` : `'${route.path}'`); setProps("id", `'${route.id}'`); setProps("index", `${route.index}`); if ((_a = route.children) == null ? undefined : _a.length) { const children = await this.routesToString(route.children, staticImport); setProps("children", children); } return `{${[...props.entries()].map(([k, v]) => `${k}:${v}`).join(",")}}`; } /** * 数据路由模式下的路由转换 */ async dataApiRouteToString(route, staticImport) { const importee = changeCase.pascalSnakeCase(route.id); const vite2 = importViteEsmSync(); const componentPath = vite2.normalizePath(path2__namespace.default.resolve(this.ctx.remixOptions.appDirectory, route.file)); const { setProps, props } = this.createPropsSetter(); setProps("path", route.index && !route.path ? `''` : `'${route.path}'`); setProps("id", `'${route.id}'`); setProps("index", `${route.index}`); if (route.hasDefaultExport) { const escapeHandle = ( /*js*/ `async () => { const { default: Component, handle, ...rest } = await import('${componentPath}'); return { Component, ...rest, }; }` ); if (this.ctx.handleAsync) { setProps("lazy", escapeHandle); setProps( "handle", /*js*/ `async () => { const { handle } = await import('${componentPath}') if (!handle) return return handle }` ); } else { setProps("lazy", escapeHandle.replace("handle,", "")); } } if (!route.hasDefaultExport) { staticImport.push(`import * as ${importee} from '${componentPath}';`); this.setDataApiToProps(props, { route, importee }); } if (route.children.length) { const children = await this.routesToString(route.children, staticImport); setProps("children", children); } return `{${[...props.entries()].map(([k, v]) => `${k}:${v}`).join(",")}}`; } /** * @description 设置 Data API 到 props */ setDataApiToProps(props, { route, importee }) { const { setProps } = this.createPropsSetter(props); setProps( "element", this.reactElementInExports(route, { importee, namedExport: "Component" }) ); setProps( "errorElement", this.reactElementInExports(route, { importee, namedExport: "ErrorBoundary" }) ); setProps("clientLoader", this.constantInExports(route, { importee, namedExport: "clientLoader" })); setProps( "loader", this.constantInExports(route, { importee, namedExport: "loader" }) ); setProps("clientAction", this.constantInExports(route, { importee, namedExport: "clientAction" })); setProps( "action", this.constantInExports(route, { importee, namedExport: "action" }) ); setProps( "handle", this.constantInExports(route, { importee, namedExport: "handle" }) ); setProps( "shouldRevalidate", this.constantInExports(route, { importee, namedExport: "shouldRevalidate" }) ); setProps( "lazy", this.constantInExports(route, { importee, namedExport: "lazy" }) ); } /** * @description 从导出中转换常量 */ constantInExports(route, { importee, namedExport, field = namedExport }) { return this.isInExports(route, field || namedExport) ? `${importee}.${namedExport}` : ""; } async processRouteManifest() { const routeManifestExports = await getRouteManifestModuleExports(this.ctx); if (this.ctx.isLegacyMode) { const routeManifest = this.ctx.routeManifest; await Promise.all( Object.entries(routeManifest).map(async ([key, route]) => { const sourceExports = routeManifestExports[key]; routeManifest[key] = { ...route, file: route.file, id: route.id, path: route.path, index: route.index, caseSensitive: route.caseSensitive, // lazy Component hasDefaultExport: sourceExports.includes("default"), // Non-Lazy Component hasComponent: sourceExports.includes("Component") }; }) ); return routeManifest; } else { const routeManifest = this.ctx.routeManifest; await Promise.all( Object.entries(routeManifest).map(async ([key, route]) => { const sourceExports = routeManifestExports[key]; routeManifest[key] = { ...route, file: route.file, id: route.id, parentId: route.parentId, path: route.path, index: route.index, caseSensitive: route.caseSensitive, /** * @see https://reactrouter.com/en/main/route/route */ hasAction: sourceExports.includes("action"), hasClientAction: sourceExports.includes("clientAction"), hasLoader: sourceExports.includes("loader"), hasClientLoader: sourceExports.includes("clientLoader"), hasHandle: sourceExports.includes("handle"), hasShouldRevalidate: sourceExports.includes("shouldRevalidate"), hasErrorBoundary: sourceExports.includes("ErrorBoundary"), /** * @ses https://reactrouter.com/en/main/route/lazy * Lazy Component */ hasLazy: sourceExports.includes("lazy"), hasComponent: sourceExports.includes("Component"), hasDefaultExport: sourceExports.includes("default") }; }) ); return routeManifest; } } getRoute(file) { const vite2 = importViteEsmSync(); const routePath = vite2.normalizePath(path2__namespace.default.relative(this.ctx.remixOptions.appDirectory, file)); const route = Object.values(this.ctx.routeManifest).find((r) => vite2.normalizePath(r.file) === routePath); return route; } /** * Adapted from `createClientRoutes` in react-router/lib/dom/ssr/routes.tsx */ createClientRoutes(routeManifest, parentId) { const routes = Object.keys(routeManifest).filter((key) => routeManifest[key].parentId === parentId).map((key) => { const route = this.createClientRoute(routeManifest[key]); route.children = this.createClientRoutes(routeManifest, route.id); return route; }); return routes; } createClientRoute(route) { return { ...route, path: route.path || "", index: !!route.index, children: [] }; } }; // src/node/virtual.ts var routesId = "virtual:remix-flat-routes"; var vmods = [routesId]; var resolvedVirtualModuleId = (virtualModuleId) => `\0${virtualModuleId}`; function invalidateVirtualModule(server, reload) { const { moduleGraph, ws } = server; vmods.forEach((vmod) => { const module = moduleGraph.getModuleById(resolvedVirtualModuleId(vmod)); if (module) { moduleGraph.invalidateModule(module); } if (reload) { ws.send({ type: "full-reload", path: "*" }); } }); } // src/node/index.ts function remixFlatRoutes(options = {}) { const { appDirectory = "app", flatRoutesOptions, routes, legacy, handleAsync = false, reactRefresh } = options; const routeDir = (flatRoutesOptions == null ? undefined : flatRoutesOptions.routeDir) || "routes"; const routeDirs = Array.isArray(routeDir) ? routeDir : [routeDir]; let isLegacyMode = legacy; if (isLegacyMode === undefined) { isLegacyMode = detectLegacyMode(); } let reactRefreshEnabled = reactRefresh; if (reactRefreshEnabled === undefined) { reactRefreshEnabled = detectReactRefresh(); } for (const routeDir2 of routeDirs) { validateRouteDir(path2__namespace.default.join(path2__namespace.default.resolve(process.cwd(), appDirectory), routeDir2)); } let viteUserConfig; let routeUtil; const ctx = { remixOptions: { appDirectory, flatRoutesOptions, routes }, isLegacyMode, handleAsync, rootDirectory: process.cwd(), routeManifest: {}, viteChildCompiler: null, viteConfig: undefined, reactRefresh: reactRefreshEnabled }; return [ { enforce: "pre", name: "vite-plugin-remix-flat-routes", /** * @see `config` in react-router-dev/vite/plugin.ts */ async config(_viteUserConfig) { await preloadViteEsm(); viteUserConfig = _viteUserConfig; }, /** * @see `configResolved` in react-router-dev/vite/plugin.ts */ async configResolved(viteConfig) { await esModuleLexer.init; ctx.viteConfig = viteConfig; if (!viteConfig || viteConfig.isProduction || viteConfig.build.ssr || viteConfig.command === "build" || viteConfig.server.hmr === false) { ctx.reactRefresh = false; } ctx.rootDirectory = viteConfig.root; ctx.remixOptions.appDirectory = path2__namespace.default.resolve(viteConfig.root, appDirectory); if (viteConfig.command === "build") { const vite2 = importViteEsmSync(); const childCompilerConfigFile = await vite2.loadConfigFromFile( { command: viteConfig.command, mode: viteConfig.mode }, viteConfig.configFile ); ctx.viteChildCompiler = await vite2.createServer({ ...viteUserConfig, mode: viteConfig.mode, server: { watch: null, preTransformRequests: false, hmr: false }, configFile: false, envFile: false, plugins: [ ...((childCompilerConfigFile == null ? undefined : childCompilerConfigFile.config.plugins) ?? []).flat().filter((plugin) => getVitePluginName(plugin) === "vite-plugin-remix-flat-routes") ] }); await ctx.viteChildCompiler.pluginContainer.buildStart({}); } }, configureServer(server) { ctx.viteChildCompiler = server; server.ws.on( "remix-flat-routes:react-refresh", (data) => { const { prevExports, nextExports } = data; if (isObjEq(prevExports, nextExports)) { return; } invalidateVirtualModule(server, true); } ); }, async resolveId(id) { if (vmods.includes(id)) { return resolvedVirtualModuleId(id); } return null; }, async load(id) { switch (id) { case resolvedVirtualModuleId(routesId): { const { routeManifest } = await resolveConfig(ctx); routeUtil = new RouteUtil({ ...ctx, routeManifest }); ctx.routeManifest = await routeUtil.processRouteManifest(); const routes2 = routeUtil.createClientRoutes(ctx.routeManifest); const { routesString, componentsString } = await routeUtil.stringifyRoutes(routes2); return { code: `import React from 'react'; ${reactRefreshHack({ appDirectory: ctx.remixOptions.appDirectory, routeManifest, viteConfig: ctx.viteConfig, enable: ctx.reactRefresh })} ${componentsString} export const routes = ${routesString}; `, map: null }; } } return null; }, /** * @see `buildEnd` in react-router-dev/vite/plugin.ts */ buildEnd() { var _a, _b, _c; (_b = (_a = ctx.viteChildCompiler) == null ? undefined : _a.httpServer) == null ? undefined : _b.close(); (_c = ctx.viteChildCompiler) == null ? undefined : _c.close(); }, async handleHotUpdate({ server, file }) { const route = routeUtil == null ? undefined : routeUtil.getRoute(file); if (route) { invalidateVirtualModule(server); } }, async watchChange(filepath, change) { if (change.event === "update") { return; } const vite2 = importViteEsmSync(); const appFileAddedOrRemoved = filepath.startsWith(vite2.normalizePath(ctx.remixOptions.appDirectory)); if (appFileAddedOrRemoved && ctx.viteChildCompiler) { invalidateVirtualModule(ctx.viteChildCompiler, true); } } }, { name: "vite-plugin-remix-flat-routes:react-refresh", enforce: "post", apply() { return ctx.reactRefresh; }, transform(code, id) { if (id === "/@react-refresh") { return { code: code.replace( "window.__getReactRefreshIgnoredExports?.({ id })", "window.__getReactRefreshIgnoredExports?.({ id, prevExports, nextExports })" ), map: null }; } } } ]; } exports.remixFlatRoutes = remixFlatRoutes;