{"version":3,"file":"plugin.js","sources":["../../../src/dev-server-plugin/plugin.ts"],"sourcesContent":["import { isRunnableDevEnvironment } from 'vite'\nimport { VIRTUAL_MODULES } from '@tanstack/start-server-core'\nimport { NodeRequest, sendNodeResponse } from 'srvx/node'\nimport { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { resolveViteId } from '../utils'\nimport { extractHtmlScripts } from './extract-html-scripts'\nimport {\n  CSS_MODULES_REGEX,\n  collectDevStyles,\n  normalizeCssModuleCacheKey,\n} from './dev-styles'\nimport type { Connect, DevEnvironment, PluginOption } from 'vite'\nimport type { GetConfigFn } from '../types'\n\nexport function devServerPlugin({\n  getConfig,\n  devSsrStylesEnabled,\n}: {\n  getConfig: GetConfigFn\n  devSsrStylesEnabled: boolean\n}): PluginOption {\n  let isTest = false\n\n  let injectedHeadScripts: string | undefined\n\n  // Cache CSS modules content during transform hook.\n  // For CSS modules, the transform hook receives the raw CSS content before\n  // Vite wraps it in JS. We capture this to use during SSR style collection.\n  const cssModulesCache: Record<string, string> = {}\n\n  return [\n    {\n      name: 'tanstack-start-core:dev-server',\n      config(_userConfig, { mode }) {\n        isTest = isTest ? isTest : mode === 'test'\n      },\n      // Capture CSS modules content during transform\n      transform: {\n        filter: {\n          id: CSS_MODULES_REGEX,\n        },\n        handler(code, id) {\n          cssModulesCache[normalizeCssModuleCacheKey(id)] = code\n        },\n      },\n      async configureServer(viteDevServer) {\n        if (isTest) {\n          return\n        }\n\n        // Extract the scripts that Vite plugins would inject into the initial HTML\n        const templateHtml = `<html><head></head><body></body></html>`\n        const transformedHtml = await viteDevServer.transformIndexHtml(\n          '/',\n          templateHtml,\n        )\n        const scripts = extractHtmlScripts(transformedHtml)\n        injectedHeadScripts = scripts\n          .flatMap((script) => script.content ?? [])\n          .join(';')\n\n        // CSS middleware registered in PRE-PHASE (before Vite's internal middlewares)\n        // This ensures it handles /@tanstack-start/styles.css before any catch-all middleware\n        // from other plugins (like nitro) that may be registered in the post-phase.\n        // This makes the CSS endpoint work regardless of plugin order in the Vite config.\n        // We check pathname.endsWith() to handle basepaths (e.g., /my-app/@tanstack-start/styles.css)\n        // since pre-phase runs before Vite's base middleware strips the basepath.\n        if (devSsrStylesEnabled) {\n          viteDevServer.middlewares.use(async (req, res, next) => {\n            const url = req.url ?? ''\n            const pathname = url.split('?')[0]\n            if (!pathname?.endsWith('/@tanstack-start/styles.css')) {\n              return next()\n            }\n\n            try {\n              // Parse route IDs from query param\n              const urlObj = new URL(url, 'http://localhost')\n              const routesParam = urlObj.searchParams.get('routes')\n              const routeIds = routesParam ? routesParam.split(',') : []\n\n              // Build entries list from route file paths\n              const entries: Array<string> = []\n\n              // Look up route file paths from manifest\n              // Only routes registered in the manifest are used - this prevents path injection\n              const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as\n                | Record<string, { filePath: string; children?: Array<string> }>\n                | undefined\n\n              if (routesManifest && routeIds.length > 0) {\n                for (const routeId of routeIds) {\n                  const route = routesManifest[routeId]\n                  if (route?.filePath) {\n                    entries.push(route.filePath)\n                  }\n                }\n              }\n\n              const css =\n                entries.length > 0\n                  ? await collectDevStyles({\n                      viteDevServer,\n                      entries,\n                      cssModulesCache,\n                    })\n                  : undefined\n\n              res.setHeader('Content-Type', 'text/css')\n              res.setHeader('Cache-Control', 'no-store')\n              res.end(css ?? '')\n            } catch (e) {\n              // Log error but still return valid CSS response to avoid MIME type issues\n              console.error('[tanstack-start] Error collecting dev styles:', e)\n              res.setHeader('Content-Type', 'text/css')\n              res.setHeader('Cache-Control', 'no-store')\n              res.end(\n                `/* Error collecting styles: ${e instanceof Error ? e.message : String(e)} */`,\n              )\n            }\n          })\n        }\n\n        return () => {\n          const serverEnv = viteDevServer.environments[\n            VITE_ENVIRONMENT_NAMES.server\n          ] as DevEnvironment | undefined\n\n          if (!serverEnv) {\n            throw new Error(\n              `Server environment ${VITE_ENVIRONMENT_NAMES.server} not found`,\n            )\n          }\n\n          const { startConfig } = getConfig()\n          const installMiddleware = startConfig.vite?.installDevServerMiddleware\n          if (installMiddleware === false) {\n            return\n          }\n          if (installMiddleware == undefined) {\n            // do not install middleware in middlewareMode by default\n            if (viteDevServer.config.server.middlewareMode) {\n              return\n            }\n\n            // do not install middleware if SSR env in case another plugin already did\n            if (\n              !isRunnableDevEnvironment(serverEnv) ||\n              // do not check via `isFetchableDevEnvironment` since nitro does implement the `FetchableDevEnvironment` interface but not via inheritance (which this helper checks)\n              'dispatchFetch' in serverEnv\n            ) {\n              return\n            }\n          }\n\n          if (!isRunnableDevEnvironment(serverEnv)) {\n            throw new Error(\n              'cannot install vite dev server middleware for TanStack Start since the SSR environment is not a RunnableDevEnvironment',\n            )\n          }\n\n          viteDevServer.middlewares.use(async (req, res) => {\n            // fix the request URL to match the original URL\n            // otherwise, the request URL will '/index.html'\n            if (req.originalUrl) {\n              req.url = req.originalUrl\n            }\n            const webReq = new NodeRequest({ req, res })\n\n            try {\n              // Import and resolve the request by running the server request entry point\n              // this request entry point must implement the `fetch` API as follows:\n              /**\n               * export default {\n               *  fetch(req: Request): Promise<Response>\n               * }\n               */\n              const serverEntry = await serverEnv.runner.import(\n                ENTRY_POINTS.server,\n              )\n              const webRes = await serverEntry['default'].fetch(webReq)\n\n              return sendNodeResponse(res, webRes)\n            } catch (e) {\n              console.error(e)\n              try {\n                viteDevServer.ssrFixStacktrace(e as Error)\n              } catch {}\n\n              if (\n                webReq.headers.get('content-type')?.includes('application/json')\n              ) {\n                return sendNodeResponse(\n                  res,\n                  new Response(\n                    JSON.stringify(\n                      {\n                        status: 500,\n                        error: 'Internal Server Error',\n                        message:\n                          'An unexpected error occurred. Please try again later.',\n                        timestamp: new Date().toISOString(),\n                      },\n                      null,\n                      2,\n                    ),\n                    {\n                      status: 500,\n                      headers: {\n                        'Content-Type': 'application/json',\n                      },\n                    },\n                  ),\n                )\n              }\n\n              return sendNodeResponse(\n                res,\n                new Response(\n                  `\n              <!DOCTYPE html>\n              <html lang=\"en\">\n                <head>\n                  <meta charset=\"UTF-8\" />\n                  <title>Error</title>\n                  <script type=\"module\">\n                    import { ErrorOverlay } from '/@vite/client'\n                    document.body.appendChild(new ErrorOverlay(${JSON.stringify(\n                      prepareError(req, e),\n                    ).replace(/</g, '\\\\u003c')}))\n                  </script>\n                </head>\n                <body>\n                </body>\n              </html>\n            `,\n                  {\n                    status: 500,\n                    headers: {\n                      'Content-Type': 'text/html',\n                    },\n                  },\n                ),\n              )\n            }\n          })\n        }\n      },\n    },\n    {\n      name: 'tanstack-start-core:dev-server:injected-head-scripts',\n      sharedDuringBuild: true,\n      applyToEnvironment: (env) => env.config.consumer === 'server',\n      resolveId: {\n        filter: { id: new RegExp(VIRTUAL_MODULES.injectedHeadScripts) },\n        handler(_id) {\n          return resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)\n        },\n      },\n      load: {\n        filter: {\n          id: new RegExp(resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)),\n        },\n        handler() {\n          const mod = `\n        export const injectedHeadScripts = ${JSON.stringify(injectedHeadScripts) || 'undefined'}`\n          return mod\n        },\n      },\n    },\n  ]\n}\n\n/**\n * Formats error for SSR message in error overlay\n * @param req\n * @param error\n * @returns\n */\nfunction prepareError(req: Connect.IncomingMessage, error: unknown) {\n  const e = error as Error\n  return {\n    message: `An error occurred while server rendering ${req.url}:\\n\\n\\t${\n      typeof e === 'string' ? e : e.message\n    } `,\n    stack: typeof e === 'string' ? '' : e.stack,\n  }\n}\n"],"names":[],"mappings":"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"}