diff --git a/e2e/react/src/components/SponsorInfo.tsx b/e2e/react/src/components/SponsorInfo.tsx
index 0dc05b7263..f10ed92a79 100644
--- a/e2e/react/src/components/SponsorInfo.tsx
+++ b/e2e/react/src/components/SponsorInfo.tsx
@@ -1,4 +1,4 @@
-import { graphql, useFragment, type SponsorInfo } from '$houdini'
+import { graphql, type SponsorInfo, useFragment } from '$houdini'
type Props = {
sponsor: SponsorInfo
diff --git a/packages/houdini-react/src/plugin/codegen/render.ts b/packages/houdini-react/src/plugin/codegen/render.ts
index 28593dd124..e175f8b344 100644
--- a/packages/houdini-react/src/plugin/codegen/render.ts
+++ b/packages/houdini-react/src/plugin/codegen/render.ts
@@ -24,11 +24,104 @@ export const renderToStream = render
`
const app_index = `
+import { Router } from '$houdini/plugins/houdini-react/runtime'
import React from 'react'
+
import Shell from '../../../../../src/+index'
-import { Router } from '$houdini'
-export default (props) =>
+export default (props) => (
+
+
+
+)
+`
+
+ let renderer = `
+ import { Cache } from '$houdini/runtime/cache/cache'
+import { serverAdapterFactory, _serverHandler } from '$houdini/runtime/router/server'
+import { HoudiniClient } from '$houdini/runtime/client'
+import { renderToStream } from 'react-streaming/server'
+import React from 'react'
+
+import { router_cache } from '../../runtime/routing'
+// @ts-expect-error
+import client from '../../../../../src/+client'
+// @ts-expect-error
+import App from './App'
+
+export const on_render =
+ ({ assetPrefix, pipe, production, documentPremable }) =>
+ async ({
+ url,
+ match,
+ session,
+ manifest,
+ }) => {
+ // instanitate a cache we can use for this request
+ const cache = new Cache({ disabled: false })
+
+ if (!match) {
+ return new Response('not found', { status: 404 })
+ }
+
+ const {
+ readable,
+ injectToStream,
+ pipe: pipeTo,
+ } = await renderToStream(
+ React.createElement(App, {
+ initialURL: url,
+ cache: cache,
+ session: session,
+ assetPrefix: assetPrefix,
+ manifest: manifest,
+ ...router_cache()
+ }),
+ {
+ userAgent: 'Vite',
+ }
+ )
+
+ // add the initial scripts to the page
+ injectToStream(\`
+
+
+ \${documentPremable}
+
+
+
+ \`)
+
+ if (pipeTo && pipe) {
+ pipeTo(pipe)
+ return true
+ } else {
+ return new Response(readable)
+ }
+ }
+
+export function reactServerHandler(options) {
+ return _serverHandler({
+ ...options,
+ client,
+ on_render: on_render({ assetPrefix: options.assetPrefix, pipe: options.pipe, documentPremable: options.documentPremable }),
+ })
+}
+
+export default function createServerAdapter(options) {
+ return serverAdapterFactory({
+ client,
+ production: true,
+ ...options,
+ on_render: on_render({ assetPrefix: options.assetPrefix }),
+ })
+}
`
// and a file that adapters can import to get the local configuration
@@ -55,74 +148,9 @@ export default (props) =>
}
`
- // we need a file in the local runtime that we can use to drive the server-side responses
- const server_adapter = `
-import React from 'react'
-import { renderToStream } from 'react-streaming/server'
-import { Cache } from '$houdini/runtime/cache/cache'
-import { serverAdapterFactory } from '$houdini/runtime/router/server'
-
-import { Router, router_cache } from '../../runtime'
-import manifest from '../../runtime/manifest'
-import App from './App'
-
-import Shell from '../../../../../src/+index'
-
-export default (options) => {
- return serverAdapterFactory({
- manifest,
- ...options,
- on_render: async ({url, match, session, pipe , manifest }) => {
- // instanitate a cache we can use for this request
- const cache = new Cache({ disabled: false })
-
- if (!match) {
- return new Response('not found', { status: 404 })
- }
-
- const { readable, injectToStream, pipe: pipeTo } = await renderToStream(
- React.createElement(App, {
- initialURL: url,
- cache: cache,
- session: session,
- assetPrefix: options.assetPrefix,
- manifest: manifest,
- ...router_cache()
- }),
- {
- userAgent: 'Vite',
- }
- )
-
- // add the initial scripts to the page
- injectToStream(\`
-
-
-
-
- \`)
-
- if (pipe && pipeTo) {
- // pipe the response to the client
- pipeTo(pipe)
- } else {
- // and deliver our Response while that's running.
- return new Response(readable)
- }
- },
- })
-}
- `
-
await Promise.all([
- fs.writeFile(routerConventions.server_adapter_path(config), server_adapter),
fs.writeFile(routerConventions.adapter_config_path(config), adapter_config),
+ fs.writeFile(routerConventions.server_adapter_path(config), renderer),
fs.writeFile(routerConventions.app_component_path(config), app_index),
fs.writeFile(routerConventions.vite_render_path(config), vite_render),
])
diff --git a/packages/houdini-react/src/plugin/vite.tsx b/packages/houdini-react/src/plugin/vite.tsx
index 839f9d1d05..b44a304229 100644
--- a/packages/houdini-react/src/plugin/vite.tsx
+++ b/packages/houdini-react/src/plugin/vite.tsx
@@ -1,31 +1,23 @@
-import * as graphql from 'graphql'
import { GraphQLSchema } from 'graphql'
-import { createYoga } from 'graphql-yoga'
import {
PluginHooks,
path,
fs,
load_manifest,
- routerConventions,
isSecondaryBuild,
type ProjectManifest,
- type ServerAdapterFactory,
type YogaServer,
type RouterManifest,
- find_match,
localApiEndpoint,
loadLocalSchema,
- handle_request,
- localApiSessionKeys,
- get_session,
- Cache,
- HoudiniClient,
+ routerConventions,
+ find_match,
+ internalRoutes,
} from 'houdini'
import React from 'react'
-import type { BuildOptions } from 'vite'
+import type { BuildOptions, Connect } from 'vite'
import { setManifest } from '.'
-import { router_cache } from '../runtime'
import { writeTsconfig } from './codegen/typeRoot'
// in order to coordinate the client and server, the client's pending request cache
@@ -172,8 +164,6 @@ export default {
})
}
-
-
// hydrate the cache with the information from the initial payload
window.__houdini__cache__?.hydrate(
window.__houdini__initial__cache__,
@@ -209,6 +199,11 @@ if (window.__houdini__nav_caches__ && window.__houdini__nav_caches__.artifact_ca
await writeTsconfig(server.houdiniConfig)
server.middlewares.use(async (req, res, next) => {
+ if (!req.url) {
+ next()
+ return
+ }
+
// import the router manifest from the runtime
// pull in the project's manifest
const { default: router_manifest } = (await server.ssrLoadModule(
@@ -218,19 +213,16 @@ if (window.__houdini__nav_caches__ && window.__houdini__nav_caches__.artifact_ca
)
)) as { default: RouterManifest }
- const graphqlEndpoint = localApiEndpoint(server.houdiniConfig.configFile)
+ const [match] = find_match(router_manifest, req.url)
- // any requests for things that aren't routes shouldn't be handled
- try {
- const [match] = find_match(router_manifest, req.url ?? '/')
- if (!match) {
- throw new Error()
- }
- } catch {
- if (req.url !== graphqlEndpoint) {
- console.log('skipping', req.url)
- return next()
- }
+ if (
+ !match &&
+ !internalRoutes(server.houdiniConfig.configFile).find((route) =>
+ req.url?.startsWith(route)
+ )
+ ) {
+ next()
+ return
}
// its worth loading the project manifest
@@ -241,6 +233,7 @@ if (window.__houdini__nav_caches__ && window.__houdini__nav_caches__.artifact_ca
if (project_manifest.local_schema) {
schema = await loadLocalSchema(server.houdiniConfig)
}
+
// import the yoga server
let yoga: YogaServer | null = null
if (project_manifest.local_yoga) {
@@ -249,112 +242,77 @@ if (window.__houdini__nav_caches__ && window.__houdini__nav_caches__.artifact_ca
'+yoga?t=' + new Date().getTime()
)
yoga = (await server.ssrLoadModule(yogaPath)) as YogaServer
- } else if (project_manifest.local_schema) {
- yoga = createYoga({
- schema: schema!,
- landingPage: true,
- graphqlEndpoint,
- })
- }
-
- if (!req.url) {
- next()
- return
}
- // pull out the desired url
- const url = req.url
+ // load the render factory
+ const { reactServerHandler: serverHandler } = (await server.ssrLoadModule(
+ routerConventions.server_adapter_path(server.houdiniConfig) +
+ '?t=' +
+ new Date().getTime()
+ )) as { reactServerHandler: any }
- console.log(yoga, url)
- // if its a req we can process with yoga, do it.
- if (yoga && url === localApiEndpoint(server.houdiniConfig.configFile)) {
- console.log('yoga response')
- return yoga(req, res)
+ const requestHeaders = new Headers()
+ for (const header of Object.entries(req.headers ?? {})) {
+ requestHeaders.set(header[0], header[1] as string)
}
- const headers = new Headers(req.headers as Record)
- const session_keys = localApiSessionKeys(server.houdiniConfig.configFile)
- // load the session information
- const session = get_session(
- headers,
- localApiSessionKeys(server.houdiniConfig.configFile)
+ // wrap the vite request in a proper on
+ const request = new Request(
+ 'https://localhost:5173' + req.url,
+ req.method === 'POST'
+ ? {
+ method: req.method,
+ headers: requestHeaders,
+ body: await getBody(req),
+ }
+ : undefined
)
- // maybe its a session-related req
- const authResponse = await handle_request({
- url,
- config: server.houdiniConfig.configFile,
- session_keys,
- headers: new Headers(Object.entries(headers)),
- })
- if (authResponse) {
- return authResponse
+ for (const [key, value] of Object.entries(req.headers)) {
+ request.headers.set(key, value as string)
}
- // the req is for a server-side rendered page
-
- // find the matching url
- const [match] = find_match(router_manifest, url)
- // instanitate a cache we can use for this request
- const cache = new Cache({ disabled: false })
-
- if (!match) {
- return new Response('not found', { status: 404 })
- }
-
- const { default: client } = (await server.ssrLoadModule(
- path.join(server.houdiniConfig.projectRoot, 'src', '+client')
- )) as { default: HoudiniClient }
- const { renderToStream } = await server.ssrLoadModule(
- routerConventions.vite_render_path(server.houdiniConfig)
- )
- const { default: App } = (await server.ssrLoadModule(
- routerConventions.app_component_path(server.houdiniConfig) +
- '?t=' +
- new Date().getTime()
- )) as { default: () => React.ReactElement }
-
- if (schema) {
- const graphqlEndpoint = localApiEndpoint(server.houdiniConfig.configFile)
- client.registerProxy(graphqlEndpoint, async ({ query, variables, session }) => {
- // get the parsed query
- const parsed = graphql.parse(query)
-
- return await graphql.execute(schema!, parsed, null, session, variables)
- })
+ // instantiate the handler and invoke it with a mocked response
+ const result: Response = await serverHandler({
+ schema,
+ yoga,
+ production: false,
+ manifest: router_manifest,
+ graphqlEndpoint: localApiEndpoint(server.houdiniConfig.configFile),
+ assetPrefix: '/virtual:houdini',
+ pipe: res,
+ documentPremable: ``,
+ })(request)
+ if (result && result.status === 404) {
+ next()
}
-
- const { injectToStream, pipe } = await renderToStream(
- React.createElement(App, {
- initialURL: url,
- cache: cache,
- session: session,
- assetPrefix: '/virtual:houdini',
- manifest: manifest,
- ...router_cache(),
- }),
- {
- userAgent: 'Vite',
+ // if we got here but we didn't pipe a response then we have to send the result to the end
+ if (result && typeof result !== 'boolean') {
+ if (res.closed) {
+ return
}
- )
-
- // add the initial scripts to the page
- injectToStream(`
-
-
-
-
-