Skip to content

Commit

Permalink
Fix HMR in React Framework (#1196)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Oct 1, 2023
1 parent bb6042a commit e92ea8f
Show file tree
Hide file tree
Showing 16 changed files with 403 additions and 230 deletions.
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

0 comments on commit e92ea8f

Please sign in to comment.