UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

455 lines (453 loc) 24.8 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: !0 }); }, __copyProps = (to, from, except, desc) => { if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target, mod )), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod); var fileSystemRouterPlugin_exports = {}; __export(fileSystemRouterPlugin_exports, { createFileSystemRouterPlugin: () => createFileSystemRouterPlugin }); module.exports = __toCommonJS(fileSystemRouterPlugin_exports); var import_node_path = __toESM(require("node:path"), 1), import_node_stream = require("node:stream"), import_perfect_debounce = require("perfect-debounce"), import_picocolors = __toESM(require("picocolors"), 1), import_vite = require("vite"), import_constants = require("../../constants"), import_createHandleRequest = require("../../createHandleRequest"), import_getPageExport = require("../../utils/getPageExport"), import_getRouterRootFromOneOptions = require("../../utils/getRouterRootFromOneOptions"), import_isResponse = require("../../utils/isResponse"), import_isStatus = require("../../utils/isStatus"), import_promiseWithResolvers = require("../../utils/promiseWithResolvers"), import_trackLoaderDependencies = require("../../utils/trackLoaderDependencies"), import_constants2 = require("../../vite/constants"), import_replaceLoader = require("../../vite/replaceLoader"), import_one_server_only = require("../one-server-only"), import_virtualEntryConstants = require("./virtualEntryConstants"); const debugRouter = process.env.ONE_DEBUG_ROUTER, debugLoaderDeps = process.env.ONE_DEBUG_LOADER_DEPS, routeTypeColors = { ssg: import_picocolors.default.green, ssr: import_picocolors.default.blue, spa: import_picocolors.default.yellow, api: import_picocolors.default.magenta }, USE_SERVER_ENV = !1; //!!process.env.USE_SERVER_ENV function createFileSystemRouterPlugin(options) { const preloads = ["/@vite/client", import_virtualEntryConstants.virtalEntryIdClient]; let runner, server; const loaderFileDependencies = /* @__PURE__ */ new Map(); let handleRequest = createRequestHandler(), renderPromise = null; function createRequestHandler() { const routerRoot = (0, import_getRouterRootFromOneOptions.getRouterRootFromOneOptions)(options); async function findNearestNotFoundPath(routeFile2) { let searchDir = routeFile2.replace(/\/[^/]+$/, ""); for (; ; ) { for (const ext of [".tsx", ".ts", ".jsx", ".js"]) { const candidate = import_node_path.default.join(routerRoot, searchDir, `+not-found${ext}`); try { if ((await runner.import(candidate))?.default) return searchDir ? `/${searchDir}/+not-found` : "/+not-found"; } catch { } } if (!searchDir) break; const parent = searchDir.replace(/\/[^/]+$/, ""); parent === searchDir ? searchDir = "" : searchDir = parent; } return "/+not-found"; } return (0, import_createHandleRequest.createHandleRequest)( { async handlePage({ route, url, loaderProps }) { if (options.server?.loggingEnabled !== !1) { const colorType = routeTypeColors[route.type] || import_picocolors.default.white, pathname = typeof url == "string" ? new URL(url).pathname : url.pathname, file = route.isNotFound ? import_picocolors.default.red("404") : import_picocolors.default.dim(`app/${route.file.slice(2)}`); console.info( ` \u24F5 ${colorType(`[${route.type}]`)} ${pathname} ${import_picocolors.default.dim("\u2192")} ${file}` ); } const layouts = route.layouts || [], isSpaShell = route.type === "spa" && layouts.some( (layout) => layout.layoutRenderMode === "ssg" || layout.layoutRenderMode === "ssr" ); if (route.type === "spa" && !isSpaShell) return `<!DOCTYPE html><html><head> ${(0, import_constants.getSpaHeaderElements)({ serverContext: { mode: "spa" } })} <script type="module" src="/@one/dev.js"></script> <script type="module" src="/@vite/client" async=""></script> <script type="module" src="/@id/__x00__virtual:one-entry" async=""></script> </head></html>`; renderPromise && await renderPromise; const { promise, resolve: resolveRender } = (0, import_promiseWithResolvers.promiseWithResolvers)(); renderPromise = promise; try { const routeFile = import_node_path.default.join(routerRoot, route.file); runner.clearCache(), globalThis.__vxrnresetState?.(); const exported = routeFile === "" ? {} : await runner.import(routeFile); async function runLoaderWithTracking(routeNode, loaderFn) { const routeId = routeNode.contextKey; if (!loaderFn) return { loaderData: void 0, routeId }; try { const tracked = await (0, import_trackLoaderDependencies.trackLoaderDependencies)(() => loaderFn(loaderProps)), routePath = loaderProps?.path || "/"; for (const dep of tracked.dependencies) { const absoluteDep = import_node_path.default.resolve(dep); loaderFileDependencies.has(absoluteDep) || (loaderFileDependencies.set(absoluteDep, /* @__PURE__ */ new Set()), server?.watcher.add(absoluteDep), debugLoaderDeps && console.info(` \u24F5 [loader-dep] watching: ${absoluteDep}`)), loaderFileDependencies.get(absoluteDep).add(routePath); } return { loaderData: tracked.result, routeId }; } catch (err) { if ((0, import_isResponse.isResponse)(err)) throw err; return console.error(`[one] Error running loader for ${routeId}:`, err), { loaderData: void 0, routeId }; } } let loaderData, matches; if (isSpaShell) { const layoutLoaderPromises = layouts.map(async (layout) => { const layoutFile = import_node_path.default.join(routerRoot, layout.contextKey), layoutExported = await runner.import(layoutFile); return runLoaderWithTracking(layout, layoutExported.loader); }); matches = (await Promise.all(layoutLoaderPromises)).map((result) => ({ routeId: result.routeId, pathname: loaderProps?.path || "/", params: loaderProps?.params || {}, loaderData: result.loaderData })), loaderData = void 0; } else { const layoutRoutes = route.layouts || [], pageRoute = { contextKey: route.file, file: route.file }, layoutLoaderPromises = layoutRoutes.map(async (layout) => { const layoutFile = import_node_path.default.join(routerRoot, layout.contextKey), layoutExported = await runner.import(layoutFile); return runLoaderWithTracking(layout, layoutExported.loader); }), pageLoaderPromise = runLoaderWithTracking(pageRoute, exported.loader), [layoutResults, pageResult] = await Promise.all([ Promise.all(layoutLoaderPromises), pageLoaderPromise ]); matches = [ ...layoutResults.map((result) => ({ routeId: result.routeId, pathname: loaderProps?.path || "/", params: loaderProps?.params || {}, loaderData: result.loaderData })), { routeId: pageResult.routeId, pathname: loaderProps?.path || "/", params: loaderProps?.params || {}, loaderData: pageResult.loaderData } ], loaderData = pageResult.loaderData; } eval("process.env.TAMAGUI_IS_SERVER = '1'"); const entry = await runner.import(import_virtualEntryConstants.virtualEntryId), render = entry.default.render; (0, import_one_server_only.setServerContext)({ loaderData, loaderProps, matches }), import_constants2.LoaderDataCache[route.file] = loaderData; const isDynamicRoute = Object.keys(route.routeKeys || {}).length > 0; let isMissingSsgSlug = !1; if (route.type === "ssg" && isDynamicRoute && exported.generateStaticParams) { const staticParams = await exported.generateStaticParams({ params: loaderProps?.params }), currentParams = loaderProps?.params || {}; isMissingSsgSlug = !staticParams.some( (sp) => Object.keys(sp).every((key) => sp[key] === currentParams[key]) ); } const is404 = route.isNotFound || !(0, import_getPageExport.getPageExport)(exported) || isMissingSsgSlug || route.type === "ssg" && isDynamicRoute && loaderData === void 0; if (isMissingSsgSlug || route.type === "ssg" && isDynamicRoute && loaderData === void 0) { let notFoundExported = {}, notFoundRoutePath = "/+not-found", searchDir = route.file.replace(/\/[^/]+$/, ""); for (; ; ) { for (const ext of [".tsx", ".ts", ".jsx", ".js"]) { const candidate = import_node_path.default.join(routerRoot, searchDir, `+not-found${ext}`); try { if (notFoundExported = await runner.import(candidate), notFoundExported?.default) { notFoundRoutePath = searchDir ? `/${searchDir}/+not-found` : "/+not-found"; break; } } catch { } } if (notFoundExported?.default || !searchDir) break; const parent = searchDir.replace(/\/[^/]+$/, ""); parent === searchDir ? searchDir = "" : searchDir = parent; } if (notFoundExported.default) { (0, import_one_server_only.setServerContext)({ loaderData: void 0, loaderProps, matches: [] }); const notFoundHtml = await render({ mode: "ssg", loaderData: void 0, loaderProps, path: notFoundRoutePath, preloads, matches: [] }); return new Response(notFoundHtml, { status: 404, headers: { "Content-Type": "text/html" } }); } return new Response("<html><body><h1>404 - Not Found</h1></body></html>", { status: 404, headers: { "Content-Type": "text/html" } }); } const html = await render({ mode: isSpaShell ? "spa-shell" : route.type === "ssg" ? "ssg" : route.type === "ssr" ? "ssr" : "spa", loaderData, loaderProps, path: loaderProps?.path || "/", preloads, matches }); return is404 ? new Response(html, { status: 404, headers: { "Content-Type": "text/html" } }) : html; } catch (err) { if ((0, import_isResponse.isResponse)(err)) return err; console.error( `SSR error while loading file ${route.file} from URL ${url.href} `, err ); const title = `Error rendering ${url.pathname} on server`, message = err instanceof Error ? err.message : `${err}`, stack = err instanceof Error && err.stack || "", subMessage = /at (useEffect|useState|useReducer|useContext|useLayoutEffect)\s*\(.*?react\.development\.js/g.test( stack ) ? ` <h2>Duplicate React Error</h2> <p style="font-size: 18px; line-height: 24px; max-width: 850px;">Note: These types of errors happen during SSR because One needs all dependencies that use React to be optimized. Find the dependency on the line after the react.development.js line below to find the failing dependency. So long as that dependency has "react" as a sub-dependency, you can add it to your package.json and One will optimize it automatically. If it doesn't list it properly, you can fix this manually by changing your vite.config.ts One plugin to add "one({ deps: { depName: true })" so One optimizes depName.</p> ` : ""; return console.error(`${title} ${message} ${stack} `), ` <html> <body style="background: #000; color: #fff; padding: 5%; font-family: monospace; line-height: 2rem;"> <h1 style="display: inline-flex; background: red; color: white; padding: 5px; margin: -5px;">${title}</h1> <h2>${message}</h2> ${subMessage} ${stack ? `<pre style="font-size: 15px; line-height: 24px; white-space: pre;"> ${stack} </pre>` : ""} </body> </html> `; } finally { resolveRender(); } }, async handleLoader({ request, route: route2, url: url2, loaderProps: loaderProps2 }) { const routeFile2 = import_node_path.default.join(routerRoot, route2.file); let transformedJS = (await server.transformRequest(routeFile2))?.code; if (!transformedJS) throw new Error("No transformed js returned"); const exported2 = await runner.import(routeFile2), isDynamicRoute2 = Object.keys(route2.routeKeys || {}).length > 0; if (route2.type === "ssg" && isDynamicRoute2 && exported2.generateStaticParams) { const staticParams = await exported2.generateStaticParams({ params: loaderProps2?.params }), currentParams = loaderProps2?.params || {}; if (!staticParams.some( (sp) => Object.keys(sp).every((key) => sp[key] === currentParams[key]) )) { const nfPath = await findNearestNotFoundPath(route2.file); return `export function loader(){return{__oneError:404,__oneErrorMessage:'Not Found',__oneNotFoundPath:${JSON.stringify(nfPath)}}}`; } } let loaderData2; if (exported2.loader) try { const tracked = await (0, import_trackLoaderDependencies.trackLoaderDependencies)( () => exported2.loader(loaderProps2) ); if (loaderData2 = tracked.result, (0, import_isResponse.isResponse)(loaderData2)) throw loaderData2; const routePath = loaderProps2?.path || "/"; for (const dep of tracked.dependencies) { const absoluteDep = import_node_path.default.resolve(dep); loaderFileDependencies.has(absoluteDep) || (loaderFileDependencies.set(absoluteDep, /* @__PURE__ */ new Set()), server?.watcher.add(absoluteDep), debugLoaderDeps && console.info(` \u24F5 [loader-dep] watching: ${absoluteDep}`)), loaderFileDependencies.get(absoluteDep).add(routePath); } } catch (err) { if ((0, import_isResponse.isResponse)(err)) throw err; if (err?.code === "ENOENT") { const nfPath = await findNearestNotFoundPath(route2.file); return `export function loader(){return{__oneError:404,__oneErrorMessage:'Not Found',__oneNotFoundPath:${JSON.stringify(nfPath)}}}`; } throw err; } loaderData2 && (transformedJS = (0, import_replaceLoader.replaceLoader)({ code: transformedJS, loaderData: loaderData2 })); const platform = url2.searchParams.get("platform"); if (platform === "ios" || platform === "android") { if (!server.environments[platform || ""]) throw new Error( `[handleLoader] No Vite environment found for platform '${platform}'` ); return `exports.loader = () => (${JSON.stringify(loaderData2)});`; } return transformedJS; }, async handleAPI({ route: route2 }) { return await runner.import(import_node_path.default.join(routerRoot, route2.file)); }, async loadMiddleware(route2) { return await runner.import(import_node_path.default.join(routerRoot, route2.contextKey)); } }, { routerRoot } ); } return { name: "one-router-fs", enforce: "post", apply: "serve", async config(userConfig) { const setting = options.optimization?.autoEntriesScanning ?? "flat"; if (setting !== !1 && handleRequest.manifest.pageRoutes) return { optimizeDeps: { /** * This adds all our routes and layouts as entries which fixes initial load making * optimizeDeps be triggered which causes hard refreshes (also on initial navigations) * * see: https://vitejs.dev/config/dep-optimization-options.html#optimizedeps-entries * and: https://github.com/remix-run/remix/pull/9921 */ entries: [ ...new Set( handleRequest.manifest.pageRoutes.flatMap((route2) => route2.isNotFound ? [] : route2.file ? setting === "flat" && route2.file.split("/").filter((x) => !x.startsWith("(")).length > 3 ? [] : [ import_node_path.default.join("./app", route2.file), ...route2.layouts?.flatMap((layout) => layout.contextKey ? [import_node_path.default.join("./app", layout.contextKey)] : []) || [] ] : []) ) ] } }; }, configureServer(serverIn) { server = serverIn, runner = (0, import_vite.createServerModuleRunner)( USE_SERVER_ENV ? server.environments.server : server.environments.ssr ); const appDir = import_node_path.default.join(process.cwd(), (0, import_getRouterRootFromOneOptions.getRouterRootFromOneOptions)(options)), fileWatcherChangeListener = (0, import_perfect_debounce.debounce)( async (type, changedPath) => { (type === "add" || type === "delete") && import_node_path.default.resolve(changedPath).startsWith(appDir) && (handleRequest = createRequestHandler()); }, 100 ); server.watcher.addListener("all", fileWatcherChangeListener); const loaderDepChangeListener = (0, import_perfect_debounce.debounce)((changedPath) => { const absolutePath = import_node_path.default.resolve(changedPath), routePaths = loaderFileDependencies.get(absolutePath); routePaths && routePaths.size > 0 && (debugLoaderDeps && console.info( ` \u24F5 [loader-dep] changed: ${absolutePath}, triggering loader refetch for routes:`, [...routePaths] ), server.hot.send({ type: "custom", event: "one:loader-data-update", data: { routePaths: [...routePaths] } })); }, 100); return server.watcher.on("change", loaderDepChangeListener), () => { server.middlewares.use(async (req, res, next) => { res.setHeader("Cache-Control", "no-store"); try { const redirects = options.web?.redirects; if (redirects) { const url2 = new URL(req.url || "", `http://${req.headers.host}`); for (const redirect of redirects) { const regexStr = `^${redirect.source.replace(/:\w+/g, "([^/]+)")}$`, match = url2.pathname.match(new RegExp(regexStr)); if (match) { let destination = redirect.destination; const params = redirect.source.match(/:\w+/g); params && params.forEach((param, index) => { destination = destination.replace(param, match[index + 1] || ""); }), debugRouter && console.info(`[one] \u21AA redirect ${url2.pathname} \u2192 ${destination}`), res.writeHead(redirect.permanent ? 301 : 302, { Location: destination }), res.end(); return; } } } const reply = await handleRequest.handler( convertIncomingMessageToRequest(req) ); if (!reply) return next(); if (typeof reply != "string" && (0, import_isResponse.isResponse)(reply)) { if (debugRouter) { const headers = {}; reply.headers.forEach((v, k) => { headers[k] = v; }), console.info(`[one] \u{1F4E4} response ${reply.status}`, headers); } if (reply.headers.forEach((value, key) => { if (key === "set-cookie") { const cookies = value.split(", "); for (const cookie of cookies) res.appendHeader("Set-Cookie", cookie); } else res.setHeader(key, value); }), (0, import_isStatus.isStatusRedirect)(reply.status)) { const location = `${reply.headers.get("location") || ""}`; if (debugRouter && console.info(`[one] \u21AA response redirect \u2192 ${location}`), location) { res.writeHead(reply.status, { Location: location }), res.end(); return; } console.error("No location provided to redirected status reply", reply); } if (res.statusCode = reply.status, res.statusMessage = reply.statusText, reply.body && reply.body.locked) { console.warn("Body is locked??", req.url), res.write(""), res.end(); return; } if (reply.body) { if (reply.body.locked) { console.warn("Body is locked??", req.url), res.end(); return; } try { import_node_stream.Readable.fromWeb(reply.body).pipe(res); } catch (err) { console.warn("Error piping reply body to response:", err), res.end(); } return; } res.end(); return; } if (reply && typeof reply == "object") { res.setHeader("Content-Type", "application/json"), res.write(JSON.stringify(reply)), res.end(); return; } res.write(reply), res.end(); return; } catch (error) { console.error(`[one] routing error ${req.url}: ${error}`), next(error); } console.warn(`SSR handler didn't send a response for url: ${req.url}`); }); }; } }; } const convertIncomingMessageToRequest = (req) => { if (!req.originalUrl) throw new Error("Can't convert: originalUrl is missing"); const urlBase = `http://${req.headers.host}`, urlString = req.originalUrl, url2 = new URL(urlString, urlBase), headers = new Headers(); for (const key in req.headers) req.headers[key] && headers.append(key, req.headers[key]); const body = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method || "") ? import_node_stream.Readable.toWeb(req) : null; return new Request(url2, { method: req.method, headers, body, // Required for streaming bodies in Node's experimental fetch: duplex: "half" }); }; //# sourceMappingURL=fileSystemRouterPlugin.js.map