-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(start-plugin-core): vite preview support #5910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export const isPreview: boolean = process.env.MODE === 'preview' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export const isPreview: boolean = process.env.MODE === 'preview' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import { pathToFileURL } from 'node:url' | ||
| import { basename, extname, join } from 'pathe' | ||
| import { NodeRequest, sendNodeResponse } from 'srvx/node' | ||
| import { VITE_ENVIRONMENT_NAMES } from '../constants' | ||
| import { getServerOutputDirectory } from '../output-directory' | ||
| import type { Plugin } from 'vite' | ||
|
|
||
| export function previewServerPlugin(): Plugin { | ||
| return { | ||
| name: 'tanstack-start-core:preview-server', | ||
| configurePreviewServer: { | ||
| // Run last so platform plugins (Cloudflare, Vercel, etc.) can register their handlers first | ||
| order: 'post', | ||
| handler(server) { | ||
| // Return a function so Vite's internal middlewares (static files, etc.) handle requests first. | ||
| // Our SSR handler only processes requests that nothing else handled. | ||
| return () => { | ||
| // Cache the server build to avoid re-importing on every request | ||
| let serverBuild: any = null | ||
|
|
||
| server.middlewares.use(async (req, res, next) => { | ||
| try { | ||
| // Lazy load server build on first request | ||
| if (!serverBuild) { | ||
| // Derive output filename from input | ||
| const serverEnv = | ||
| server.config.environments[VITE_ENVIRONMENT_NAMES.server] | ||
| const serverInput = | ||
| serverEnv?.build.rollupOptions.input ?? 'server' | ||
|
|
||
| if (typeof serverInput !== 'string') { | ||
| throw new Error('Invalid server input. Expected a string.') | ||
| } | ||
|
|
||
| // Get basename without extension and add .js | ||
| const outputFilename = `${basename(serverInput, extname(serverInput))}.js` | ||
| const serverOutputDir = getServerOutputDirectory(server.config) | ||
| const serverEntryPath = join(serverOutputDir, outputFilename) | ||
| const imported = await import( | ||
| pathToFileURL(serverEntryPath).toString() | ||
| ) | ||
|
|
||
| serverBuild = imported.default | ||
| } | ||
|
|
||
| const webReq = new NodeRequest({ req, res }) | ||
| const webRes: Response = await serverBuild.fetch(webReq) | ||
|
|
||
| // Temporary workaround | ||
| // Vite preview's compression middleware doesn't support flattened array headers that srvx sets | ||
| // Call writeHead() before srvx to avoid corruption | ||
| res.setHeaders(webRes.headers) | ||
| res.writeHead(webRes.status, webRes.statusText) | ||
|
|
||
| return sendNodeResponse(res, webRes) | ||
|
Comment on lines
+52
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard During 🤖 Prompt for AI Agents |
||
| } catch (error) { | ||
| next(error) | ||
| } | ||
| }) | ||
| } | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For context:
Vite preview server uses
@polka/compressionto handle compression. This middleware overwrites theres.writeHeadmethod, which expects headers to be an object and uses afor...inloop to iterate over them.However,
srvxpasses headers as a flattened array towriteHead(). When the middleware'sfor...inloop runs through an array, it iterates over array indices ('0','1','2', ...) instead of the actual header names.This causes header corruption where a header like:
Becomes:
The workaround is to call
res.writeHead()directly beforesendNodeResponse(), which setsres.headersSent = trueand causessendNodeResponse()to skip header writing entirely.I will try to submit a patch to either Vite or
@polka/compression, but we will likely want to keep this workaround for compatibility with earlier versions of Vite.