Skip to content
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

Fix HMR in React Framework #1196

Merged
merged 16 commits into from
Oct 1, 2023
5 changes: 5 additions & 0 deletions .changeset/soft-impalas-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini-react': patch
---

Fix hot module reloading
2 changes: 1 addition & 1 deletion e2e/react/src/components/SponsorInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { graphql, useFragment, type SponsorInfo } from '$houdini'
import { graphql, type SponsorInfo, useFragment } from '$houdini'

type Props = {
sponsor: SponsorInfo
Expand Down
2 changes: 1 addition & 1 deletion packages/houdini-adapter-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"vitest": "^0.28.3"
},
"scripts": {
"build": "tsup src/index.ts src/worker.ts --format esm,cjs --external ../\\$houdini --external ../src --external graphql --minify --dts --clean --out-dir build",
"build": "tsup src/index.ts src/worker.ts --format esm,cjs --external vite --external ../\\$houdini --external ../src --external graphql --minify --dts --clean --out-dir build",
"build:": "cd ../../ && ((run build && cd -) || (cd - && exit 1))",
"build:build": "pnpm build: && pnpm build"
},
Expand Down
162 changes: 93 additions & 69 deletions packages/houdini-react/src/plugin/codegen/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,109 @@ export async function generate_renders({
config: Config
manifest: ProjectManifest
}) {
const adapter_path = routerConventions.server_adapter_path(config)

// make sure the necessary directories exist
await fs.mkdirp(path.dirname(routerConventions.server_adapter_path(config)))

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) => <Shell><Router {...props} /></Shell>
export default (props) => (
<Shell>
<Router {...props} />
</Shell>
)
`

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"
import router_manifest from '$houdini/plugins/houdini-react/runtime/manifest'

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(\`
<script>
window.__houdini__initial__cache__ = \${cache.serialize()};
window.__houdini__initial__session__ = \${JSON.stringify(session)};
</script>

\${documentPremable ?? ''}

<!--
add a virtual module that hydrates the client and sets up the initial pending cache.
the dynamic extension is to support dev which sees the raw jsx, and production which sees the bundled asset
-->
<script type="module" src="\${assetPrefix}/pages/\${match.id}.\${production ? 'js' : 'jsx'}" async=""></script>
\`)

if (pipeTo && pipe) {
pipeTo(pipe)
return true
} else {
return new Response(readable)
}
}

export function createServerAdapter(options) {
return serverAdapterFactory({
client,
production: true,
manifest: router_manifest,
on_render: on_render(options),
...options,
})
}
`

// and a file that adapters can import to get the local configuration
let adapter_config = `
import createAdapter from './server'
import { createServerAdapter as createAdapter } from './server'

export const endpoint = ${JSON.stringify(localApiEndpoint(config.configFile))}

Expand All @@ -49,74 +138,9 @@ export default (props) => <Shell><Router {...props} /></Shell>
}
`

// 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(\`
<script>
window.__houdini__initial__cache__ = \${cache.serialize()};
window.__houdini__initial__session__ = \${JSON.stringify(session)};
</script>

<!--
add a virtual module that hydrates the client and sets up the initial pending cache.
the dynamic extension is to support dev which sees the raw jsx, and production which sees the bundled asset
-->
<script type="module" src="\${options.assetPrefix}/pages/\${match.id}.\${options.production ? 'js' : 'jsx'}" async=""></script>
\`)

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(adapter_path, renderer),
fs.writeFile(routerConventions.app_component_path(config), app_index),
])
}
Loading
Loading