-
Notifications
You must be signed in to change notification settings - Fork 66
feat(ssr): defer serving and build wiring to external servers #283
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
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| --- | ||
| 'vite-plugin-solid': patch | ||
| --- | ||
|
|
||
| Turnkey SSR: let an external server own serving and the build. | ||
|
|
||
| - Configuring `environments.ssr.build.rollupOptions.input` now signals that a | ||
| server integration (nitro, a Cloudflare worker, a custom harness) owns the | ||
| server. Turnkey still resolves/generates the entries, emits | ||
| `virtual:solid-ssr-handler` and configures the client manifest, but stops | ||
| overriding the ssr build input, the `dist/*` outDirs and the `builder` flag, | ||
| and stops registering its dev HTML middleware. Previously the ssr input was | ||
| overwritten with the handler unconditionally, so the integration was left | ||
| with no server entry of its own — with nitro that means no renderer route is | ||
| registered and every page 404s in preview. | ||
| - The entry graph's dev stylesheets are now also exposed on a dev-only | ||
| endpoint, and the generated handler fetches it when the caller passes no | ||
| `devHead`. Entry CSS is collected in the dev HTML middleware, which never | ||
| runs when something else serves, and the handler typically runs in another | ||
| runtime (a dev worker, workerd) that can reach neither Vite's module graph | ||
| nor the `globalThis` resolver registry — so dev SSR streamed unstyled markup | ||
| and flashed on load. Collection stays per-request, so HMR-edited CSS is | ||
| current. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,9 @@ export interface SsrOptions { | |
| // Server-only turnkey request handler; also the server bundle's entry so a | ||
| // production server is one import away from `Request -> Response`. | ||
| const HANDLER_ID = 'virtual:solid-ssr-handler'; | ||
| // Dev-only endpoint serving the entry graph's compiled stylesheets, so a | ||
| // handler running outside this process can still inline them (see below). | ||
| const DEV_ENTRY_CSS_PATH = '/__solid_dev_entry_css'; | ||
| // Generated default entries / document shell. The `.tsx` suffix routes them | ||
| // through the plugin's normal JSX transform (per-environment SSR/DOM | ||
| // compile), exactly like user-authored entry files. | ||
|
|
@@ -178,6 +181,7 @@ export function ssrServe( | |
| let root = process.cwd(); | ||
| let base = '/'; | ||
| let isBuild = false; | ||
| let externalServer = false; | ||
| let entries: ResolvedEntries | undefined; | ||
|
|
||
| function requireEntries(): ResolvedEntries { | ||
|
|
@@ -344,7 +348,23 @@ export function ssrServe( | |
| const devHead = | ||
| `<script>${devStylePatch}</script>` + | ||
| `<script type="module" src="${joinBase(base, '/@vite/client')}"></script>`; | ||
| lines.push(``, `const DEV_HEAD = ${JSON.stringify(devHead)};`); | ||
| lines.push( | ||
| ``, | ||
| `const DEV_HEAD = ${JSON.stringify(devHead)};`, | ||
| ``, | ||
| // Fallback for callers that cannot collect the styles themselves: | ||
| // an external server's handler runs in its own runtime (a dev | ||
| // worker, workerd) with no access to this module graph, so it asks | ||
| // the dev server over HTTP. Per request, so HMR edits stay current. | ||
| `async function fetchDevEntryStyles(request) {`, | ||
| ` try {`, | ||
| ` const response = await fetch(new URL(${JSON.stringify(DEV_ENTRY_CSS_PATH)}, request.url));`, | ||
| ` return response.ok ? await response.text() : '';`, | ||
| ` } catch {`, | ||
| ` return '';`, | ||
| ` }`, | ||
| `}`, | ||
| ); | ||
| } | ||
|
|
||
| lines.push( | ||
|
|
@@ -421,6 +441,9 @@ export function ssrServe( | |
| isBuild | ||
| ? ` const clientEntry = options.clientEntry || resolveClientEntry();` | ||
| : ` const clientEntry = options.clientEntry || ${JSON.stringify(devClientEntryUrl())};`, | ||
| isBuild | ||
| ? ` const devHead = options.devHead;` | ||
| : ` const devHead = options.devHead !== undefined ? options.devHead : await fetchDevEntryStyles(request);`, | ||
| ` return provideRequestEvent({ request, locals: {} }, async () => {`, | ||
| ` let result = entry.render(request, { clientEntry, ...options.context });`, | ||
| // renderToStream results are thenables whose then() waits for the | ||
|
|
@@ -430,7 +453,7 @@ export function ssrServe( | |
| ` result = await result;`, | ||
| ` }`, | ||
| ` if (result instanceof Response) return result;`, | ||
| ` return htmlResponse(result, clientEntry, options.devHead, options.responseInit);`, | ||
| ` return htmlResponse(result, clientEntry, devHead, options.responseInit);`, | ||
| ` });`, | ||
| `}`, | ||
| ); | ||
|
|
@@ -453,33 +476,51 @@ export function ssrServe( | |
| const scanEntries = entries.generated | ||
| ? [entries.app!, ...(entries.document ? [entries.document] : [])] | ||
| : [path.resolve(root, entries.entryClient)]; | ||
| // A configured ssr input means an external server integration owns | ||
| // the server: it drives the build and serves requests, adapting | ||
| // `virtual:solid-ssr-handler` to its own entry contract. Turnkey then | ||
| // contributes only the entries, the handler and the client manifest — | ||
| // overriding the ssr input here would leave that integration without a | ||
| // server entry, and its own build/serving pipeline in conflict. | ||
| externalServer = userConfig.environments?.ssr?.build?.rollupOptions?.input != null; | ||
| return { | ||
| // No index.html: dev must not fall back to SPA-serving one, and | ||
| // the dep scanner needs explicit entries instead. | ||
| appType: 'custom', | ||
| ...(build | ||
| ? { | ||
| environments: { | ||
| client: { | ||
| build: { | ||
| manifest: true, | ||
| outDir: 'dist/client', | ||
| rollupOptions: { input: clientInput }, | ||
| ? externalServer | ||
| ? { | ||
| environments: { | ||
| client: { | ||
| build: { | ||
| manifest: true, | ||
| rollupOptions: { input: clientInput }, | ||
| }, | ||
| }, | ||
| }, | ||
| ssr: { | ||
| build: { | ||
| outDir: 'dist/server', | ||
| rollupOptions: { input: { server: HANDLER_ID } }, | ||
| } | ||
| : { | ||
| environments: { | ||
| client: { | ||
| build: { | ||
| manifest: true, | ||
| outDir: 'dist/client', | ||
| rollupOptions: { input: clientInput }, | ||
| }, | ||
| }, | ||
| ssr: { | ||
| build: { | ||
| outDir: 'dist/server', | ||
| rollupOptions: { input: { server: HANDLER_ID } }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| // Presence of `builder` makes a plain `vite build` build the | ||
| // whole app (all environments: client then ssr) on Vite 6+. | ||
| // A classic `vite build --ssr` invocation must stay a | ||
| // single-environment build, so it doesn't get the flag. | ||
| ...(env.isSsrBuild ? {} : { builder: {} }), | ||
| } | ||
| // Presence of `builder` makes a plain `vite build` build the | ||
| // whole app (all environments: client then ssr) on Vite 6+. | ||
| // A classic `vite build --ssr` invocation must stay a | ||
| // single-environment build, so it doesn't get the flag. | ||
| ...(env.isSsrBuild ? {} : { builder: {} }), | ||
| } | ||
| : { | ||
| optimizeDeps: { entries: scanEntries }, | ||
| }), | ||
|
|
@@ -526,10 +567,28 @@ export function ssrServe( | |
| ? [app!, ...(document ? [document] : [])] | ||
| : [path.resolve(root, entryServer)]; | ||
| }; | ||
| // Exposed for handlers that run outside this process (see | ||
| // fetchDevEntryStyles in the generated handler); registered even when | ||
| // an external server owns HTML, which is exactly when it is needed. | ||
| server.middlewares.use(DEV_ENTRY_CSS_PATH, (_req, res) => { | ||
| collectDevStyles(server, styleRoots()).then( | ||
| (styles) => { | ||
| res.setHeader('content-type', 'text/html; charset=utf-8'); | ||
| res.setHeader('cache-control', 'no-store'); | ||
| res.end(styles.map(renderDevStyleTag).join('')); | ||
| }, | ||
| () => { | ||
| res.statusCode = 500; | ||
| res.end(''); | ||
| }, | ||
| ); | ||
| }); | ||
| // Post middleware: Vite's own middlewares (transforms, static, the | ||
| // server-function endpoint) run first; whatever asks for HTML after | ||
| // that gets the streamed SSR render. | ||
| return () => { | ||
| // An external server (see `externalServer` above) serves HTML itself. | ||
| if (externalServer) return; | ||
|
Contributor
Author
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. hmm maybe we can avoid returning early here. not sure |
||
| server.middlewares.use((req, res, next) => { | ||
| if (req.method !== 'GET') return next(); | ||
| const accept = req.headers.accept || ''; | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
hmm this feels a bit weird to do