Skip to content

Commit

Permalink
Stabilize react deployments (#1216)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlecAivazis committed Oct 12, 2023
1 parent 41e9c6c commit d7fe2be
Show file tree
Hide file tree
Showing 16 changed files with 129 additions and 106 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-lemons-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini-react': patch
---

Stabilize react deployments
5 changes: 5 additions & 0 deletions .changeset/yellow-tables-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini-react': patch
---

Fix session redirects
12 changes: 7 additions & 5 deletions e2e/react/houdini.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ const config = {
},

plugins: {
'houdini-react': {
auth: {
redirect: '/auth/token',
sessionKeys: ['supersecret'],
},
'houdini-react': {},
},

router: {
auth: {
redirect: '/auth/token',
sessionKeys: ['supersecret'],
},
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

.env.local
.env.production.local
.env.*.local


node_modules
dist
dist-ssr
*.local

./$houdini
./dist
$houdini

# Editor directories and files
.vscode/*
Expand Down
78 changes: 39 additions & 39 deletions packages/create-houdini/templates/react-typescript/src/+index.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
import React from "react";
import React from 'react'

export default function App({ children }) {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
type="image/png"
href="https://houdinigraphql.com/images/logo.png"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css"
/>
<title>Houdini • React</title>
</head>
<body>
<ErrorBoundary>{children}</ErrorBoundary>
</body>
</html>
);
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
type="image/png"
href="https://houdinigraphql.com/images/logo.png"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css"
/>
<title>Houdini • React</title>
</head>
<body>
<ErrorBoundary>{children}</ErrorBoundary>
</body>
</html>
)
}

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
constructor(props: { children: React.ReactNode }) {
super(props)
this.state = { hasError: false }
}

static getDerivedStateFromError(error) {
return { hasError: true };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true }
}

componentDidCatch(error, info) {
console.error("ErrorBoundary caught an error:", error, info);
}
componentDidCatch(error: Error, info: {}) {
console.error('ErrorBoundary caught an error:', error, info)
}

render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>
}
return this.props.children
}
}
4 changes: 4 additions & 0 deletions packages/create-houdini/templates/react/.meta.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

.env.local
.env.production.local
.env.*.local

node_modules
dist
dist-ssr
Expand Down
5 changes: 2 additions & 3 deletions packages/houdini-adapter-cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { fileURLToPath } from 'node:url'

const adapter: Adapter = async ({ adapterPath, outDir, sourceDir }) => {
// the first thing we have to do is copy the source directory over
await fs.recursiveCopy(sourceDir, outDir)
await fs.recursiveCopy(sourceDir, path.join(outDir, 'assets'))

// read the contents of the worker file
let workerContents = (await fs.readFile(sourcePath('./worker.js')))!

// if the project has a local schema, replace the schema import string with the
// import
// make sure that the adapter module imports from the correct path
workerContents = workerContents.replaceAll('houdini/adapter', adapterPath)

await fs.writeFile(path.join(outDir, '_worker.js'), workerContents!)
Expand Down
1 change: 0 additions & 1 deletion packages/houdini-adapter-cloudflare/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const server_adapter = createServerAdapter({

const handlers: ExportedHandler = {
async fetch(req, env: any, ctx) {
// if we aren't loading an asset, push the request through our router
const url = new URL(req.url).pathname

// we are handling an asset
Expand Down
2 changes: 1 addition & 1 deletion packages/houdini-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@
},
"main": "./build/plugin-cjs/index.js",
"types": "./build/plugin/index.d.ts"
}
}
27 changes: 15 additions & 12 deletions packages/houdini-react/src/plugin/vite.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { GraphQLSchema } from 'graphql'
import {
PluginHooks,
path,
fs,
load_manifest,
isSecondaryBuild,
type ProjectManifest,
type YogaServer,
type RouterManifest,
localApiEndpoint,
loadLocalSchema,
routerConventions,
find_match,
internalRoutes,
} from 'houdini'
import React from 'react'
import { build, type BuildOptions, type Connect } from 'vite'
Expand Down Expand Up @@ -117,11 +111,6 @@ export default {
},

async closeBundle(this, config) {
// only build in production one
if (isSecondaryBuild() || devServer) {
return
}

// tell the user what we're doing
console.log('🎩 Generating Server Assets...')

Expand Down Expand Up @@ -305,7 +294,21 @@ if (window.__houdini__nav_caches__ && window.__houdini__nav_caches__.artifact_ca
for (const header of Object.entries(result.headers ?? {})) {
res.setHeader(header[0], header[1])
}
res.write(await result.text())
// handle redirects
if (result.status >= 300 && result.status < 400) {
res.writeHead(result.status, {
Location: result.headers.get('Location') ?? '',
...[...result.headers.entries()].reduce(
(headers, [key, value]) => ({
...headers,
[key]: value,
}),
{}
),
})
} else {
res.write(await result.text())
}
res.end()
}
})
Expand Down
30 changes: 10 additions & 20 deletions packages/houdini-react/src/runtime/server/renderToStream.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from 'react'
import type {
renderToPipeableStream as RenderToPipeableStream,
renderToReadableStream as RenderToReadableStream,
import {
renderToPipeableStream,
renderToReadableStream,
} from 'react-dom/server'

import { createPipeWrapper, Pipe } from './renderToStream/createPipeWrapper'
import { createReadableWrapper } from './renderToStream/createReadableWrapper'
import { nodeStreamModuleIsAvailable } from './renderToStream/loadNodeStreamModule'
import { resolveSeoStrategy, SeoStrategy } from './renderToStream/resolveSeoStrategy'
import { createDebugger } from './utils'

Expand All @@ -21,8 +20,8 @@ type Options = {
seoStrategy?: SeoStrategy
userAgent?: string
onBoundaryError?: (err: unknown) => void
renderToReadableStream?: typeof RenderToReadableStream
renderToPipeableStream?: typeof RenderToPipeableStream
renderToReadableStream?: typeof renderToReadableStream
renderToPipeableStream?: typeof renderToPipeableStream
}
type Result = (
| {
Expand Down Expand Up @@ -55,7 +54,7 @@ async function renderToStream(element: React.ReactNode, options: Options = {}):

const disable =
globalConfig.disable || (options.disable ?? resolveSeoStrategy(options).disableStream)
const webStream = options.webStream ?? !(await nodeStreamModuleIsAvailable())
const webStream = true
debug(`disable === ${disable} && webStream === ${webStream}`)

let result: Result
Expand All @@ -80,7 +79,7 @@ async function renderToNodeStream(
options: {
debug?: boolean
onBoundaryError?: (err: unknown) => void
renderToPipeableStream?: typeof RenderToPipeableStream
renderToPipeableStream?: typeof renderToPipeableStream
}
) {
debug('creating Node.js Stream Pipe')
Expand Down Expand Up @@ -109,12 +108,8 @@ async function renderToNodeStream(
}
})
}
const renderToPipeableStream =
options.renderToPipeableStream ??
// @ts-ignore
// We don't directly use import() because it shouldn't be bundled for Cloudflare Workers: the module react-dom/server.node contains a require('stream') which fails on Cloudflare Workers
((await import('react-dom/server.node'))
.renderToPipeableStream as typeof RenderToPipeableStream)

console.log("THIS ->", renderToPipeableStream)

const { pipe: pipeOriginal } = renderToPipeableStream(element, {
onShellReady() {
Expand Down Expand Up @@ -160,7 +155,7 @@ async function renderToWebStream(
options: {
debug?: boolean
onBoundaryError?: (err: unknown) => void
renderToReadableStream?: typeof RenderToReadableStream
renderToReadableStream?: typeof renderToReadableStream
}
) {
debug('creating Web Stream Pipe')
Expand All @@ -178,11 +173,6 @@ async function renderToWebStream(
}
})
}
const renderToReadableStream =
options.renderToReadableStream ??
// We directly use import() because it needs to be bundled for Cloudflare Workers
((await import('react-dom/server.browser' as string))
.renderToReadableStream as typeof RenderToReadableStream)

const readableOriginal = await renderToReadableStream(element, { onError })
const { allReady } = readableOriginal
Expand Down
2 changes: 1 addition & 1 deletion packages/houdini/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export class Config {
}

get routerBuildDirectory() {
return path.join(this.projectRoot, 'dist', 'assets')
return path.join(this.projectRoot, 'dist')
}

get definitionsDocumentsPath() {
Expand Down
9 changes: 5 additions & 4 deletions packages/houdini/src/lib/router/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export function internalRoutes(config: ConfigFile): string[] {
}

export async function buildLocalSchema(config: Config): Promise<void> {
// before we build the local schcema, we need to generate the typescript config file
// so that we can resolve all of the necessary imports

// load the current version of vite
const { build } = await import('vite')

Expand All @@ -32,15 +35,13 @@ export async function buildLocalSchema(config: Config): Promise<void> {
input: {
schema: path.join(config.localApiDir, '+schema'),
},
external: ['graphql'],
output: {
entryFileNames: 'assets/[name].js',
},
},
ssr: true,
lib: {
entry: {
schema: path.join(config.localApiDir, '+schema'),
},
entry: path.join(config.localApiDir, '+schema'),
formats: ['es'],
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/houdini/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export type PluginHooks = {
buildStart?: (
this: PluginContext,
options: NormalizedInputOptions & { houdiniConfig: Config }
) => void
) => void | Promise<void>

buildEnd?: (
this: PluginContext,
Expand Down
19 changes: 11 additions & 8 deletions packages/houdini/src/runtime/router/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ type ServerHandlerArgs = {
// so we want a single function that can be called to get the server
export async function handle_request(args: ServerHandlerArgs): Promise<Response | undefined> {
const plugin_config = args.config.router ?? {}

// if the project is configured to authorize users by redirect then
// we might need to set the session value
if (
Expand All @@ -25,18 +24,22 @@ export async function handle_request(args: ServerHandlerArgs): Promise<Response
}
}

async function redirect_auth(args: ServerHandlerArgs): Promise<Response | undefined> {
async function redirect_auth(args: ServerHandlerArgs): Promise<Response> {
// the session and configuration are passed as query parameters in
// the url
const { searchParams } = new URL(args.url!, `http://${args.headers.get('host')}`)
const { searchParams, host } = new URL(args.url!, `http://${args.headers.get('host')}`)
const { redirectTo, ...session } = Object.fromEntries(searchParams.entries())

// encode the session information as a cookie in the response and redirect the user
if (redirectTo) {
const response = Response.redirect(redirectTo, 302)
await set_session(args, response, session)
return response
}
const response = new Response('ok', {
status: 302,
headers: {
Location: redirectTo ?? '/',
},
})
await set_session(args, response, session)

return response
}

export type Server = {
Expand Down
Loading

0 comments on commit d7fe2be

Please sign in to comment.