Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Mar 29, 2024
1 parent e301ef9 commit 4e1c59c
Show file tree
Hide file tree
Showing 115 changed files with 604 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shipDataStorage } from '../server/async-storage.js'
import { ShipDetails } from './ship-details.js'
import { SearchResults } from './ship-search-results.js'

export function Document() {
export async function Document() {
return h(
'html',
{ lang: 'en' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shipDataStorage } from '../server/async-storage.js'
import { ShipDetails, ShipFallback } from './ship-details.js'
import { SearchResults, SearchResultsFallback } from './ship-search-results.js'

export function Document() {
export async function Document() {
return h(
'html',
{ lang: 'en' },
Expand Down
2 changes: 1 addition & 1 deletion exercises/01.exercises/05.problem.bootstrap/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shipDataStorage } from '../server/async-storage.js'
import { ShipDetails, ShipFallback } from './ship-details.js'
import { SearchResults, SearchResultsFallback } from './ship-search-results.js'

export function Document() {
export async function Document() {
return h(
'html',
{ lang: 'en' },
Expand Down
2 changes: 1 addition & 1 deletion exercises/01.exercises/05.solution.bootstrap/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shipDataStorage } from '../server/async-storage.js'
import { ShipDetails, ShipFallback } from './ship-details.js'
import { SearchResults, SearchResultsFallback } from './ship-search-results.js'

export function Document() {
export async function Document() {
return h(
'html',
{ lang: 'en' },
Expand Down
2 changes: 1 addition & 1 deletion exercises/01.exercises/06.problem.import-map/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shipDataStorage } from '../server/async-storage.js'
import { ShipDetails, ShipFallback } from './ship-details.js'
import { SearchResults, SearchResultsFallback } from './ship-search-results.js'

export function Document() {
export async function Document() {
return h(
'html',
{ lang: 'en' },
Expand Down
3 changes: 3 additions & 0 deletions exercises/01.exercises/06.solution.import-map/server/ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ app.head('/', (req, res) => res.status(200).end())

app.use(express.static('public'))
app.use('/js/src', express.static('src'))

// we have to server this file from our own server so dynamic imports are
// relative to our own server (this module is what loads client-side modules!)
app.use('/js/react-server-dom-esm/client', (req, res) => {
const require = createRequire(import.meta.url)
const pkgPath = require.resolve('react-server-dom-esm')
Expand Down
2 changes: 1 addition & 1 deletion exercises/01.exercises/06.solution.import-map/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shipDataStorage } from '../server/async-storage.js'
import { ShipDetails, ShipFallback } from './ship-details.js'
import { SearchResults, SearchResultsFallback } from './ship-search-results.js'

export function Document() {
export async function Document() {
return h(
'html',
{ lang: 'en' },
Expand Down
1 change: 1 addition & 0 deletions exercises/01.exercises/07.solution.module-graph/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hydrate
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "exercises__sep__01.exercises__sep__10.solution.module-graph",
"name": "exercises__sep__01.exercises__sep__07.solution.module-graph",
"version": "1.0.0",
"type": "module",
"private": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ app.head('/', (req, res) => res.status(200).end())

app.use(express.static('public'))
app.use('/js/src', express.static('src'))

// we have to server this file from our own server so dynamic imports are
// relative to our own server (this module is what loads client-side modules!)
app.use('/js/react-server-dom-esm/client', (req, res) => {
const require = createRequire(import.meta.url)
const pkgPath = require.resolve('react-server-dom-esm')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ShipDetails, ShipFallback, ShipError } from './ship-details.js'
import { SearchResults, SearchResultsFallback } from './ship-search-results.js'
import { ShipSearch } from './ship-search.js'

export function Document() {
export async function Document() {
return h(
'html',
{ lang: 'en' },
Expand Down
1 change: 1 addition & 0 deletions exercises/01.exercises/08.solution.hydrate/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hydrate
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ const ssrServer = spawnScript(

const rscServer = spawnScript(
'node',
['--watch', '--conditions=react-server', 'server/rsc.js'],
[
'--watch',
'--import',
'./server/register-rsc-loader.js',
'--conditions=react-server',
'server/rsc.js',
],
{ PORT: RSC_PORT },
chalk.green.bgBlack('RSC'),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "exercises__sep__01.exercises__sep__10.problem.module-graph",
"name": "exercises__sep__01.exercises__sep__08.solution.hydrate",
"version": "1.0.0",
"type": "module",
"private": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { register } from 'node:module'

register('./rsc-loader.js', import.meta.url)
23 changes: 23 additions & 0 deletions exercises/01.exercises/08.solution.hydrate/server/rsc-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { resolve, load as reactLoad } from 'react-server-dom-esm/node-loader'

export { resolve }

async function textLoad(url, context, defaultLoad) {
const result = await defaultLoad(url, context, defaultLoad)
if (result.format === 'module') {
if (typeof result.source === 'string') {
return result
}
return {
source: Buffer.from(result.source).toString('utf8'),
format: 'module',
}
}
return result
}

export async function load(url, context, defaultLoad) {
return await reactLoad(url, context, (u, c) => {
return textLoad(u, c, defaultLoad)
})
}
172 changes: 172 additions & 0 deletions exercises/01.exercises/08.solution.hydrate/server/ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import http from 'node:http'
import { createRequire } from 'node:module'
import path from 'node:path'
import closeWithGrace from 'close-with-grace'
import compress from 'compression'
import express from 'express'
import { createElement as h, use } from 'react'
import { renderToPipeableStream } from 'react-dom/server'
import { createFromNodeStream } from 'react-server-dom-esm/client'
import { RouterContext } from '../src/router.js'

const moduleBasePath = new URL('../src', import.meta.url).href

const PORT = process.env.PORT || 3000
const RSC_PORT = process.env.RSC_PORT || 3001
const RSC_ORIGIN = new URL(`http://localhost:${RSC_PORT}`)

const app = express()

app.use(compress())

function request(options, body) {
return new Promise((resolve, reject) => {
const req = http.request(options, res => {
resolve(res)
})
req.on('error', e => {
reject(e)
})
body.pipe(req)
})
}

app.head('/', (req, res) => res.status(200).end())

app.use(express.static('public'))
app.use('/js/src', express.static('src'))

// we have to server this file from our own server so dynamic imports are
// relative to our own server (this module is what loads client-side modules!)
app.use('/js/react-server-dom-esm/client', (req, res) => {
const require = createRequire(import.meta.url)
const pkgPath = require.resolve('react-server-dom-esm')
const modulePath = path.join(
path.dirname(pkgPath),
'esm',
'react-server-dom-esm-client.browser.development.js',
)
res.sendFile(modulePath)
})

app.all('/:shipId?', async function (req, res) {
// Proxy the request to the rsc server.
const proxiedHeaders = {
'X-Forwarded-Host': req.hostname,
'X-Forwarded-For': req.ips,
'X-Forwarded-Port': PORT,
'X-Forwarded-Proto': req.protocol,
}
if (req.get('Content-Type')) {
proxiedHeaders['Content-Type'] = req.get('Content-Type')
}

const promiseForData = request(
{
host: RSC_ORIGIN.hostname,
port: RSC_ORIGIN.port,
method: req.method,
path: req.url,
headers: proxiedHeaders,
},
req,
)

if (req.accepts('text/html')) {
try {
const rscResponse = await promiseForData
const moduleBaseURL = '/js/src'

// For HTML, we're a "client" emulator that runs the client code,
// so we start by consuming the RSC payload. This needs the local file path
// to load the source files from as well as the URL path for preloads.

let contentPromise
function Root() {
contentPromise ??= createFromNodeStream(
rscResponse,
moduleBasePath,
moduleBaseURL,
)
const content = use(contentPromise)
return content.root
}
const location = req.url
const navigate = () => {
throw new Error('navigate cannot be called on the server')
}
const isPending = false
const routerValue = {
location,
nextLocation: location,
navigate,
isPending,
}
const { pipe } = renderToPipeableStream(
h(RouterContext.Provider, { value: routerValue }, h(Root)),
{
bootstrapModules: ['/js/src/index.js'],
importMap: {
imports: {
react:
'https://esm.sh/[email protected]?pin=v126&dev',
'react-dom':
'https://esm.sh/[email protected]?pin=v126&dev',
'react-dom/':
'https://esm.sh/[email protected]&pin=v126&dev/',
'react-error-boundary':
'https://esm.sh/[email protected]?pin=126&dev',
'react-server-dom-esm/client': '/js/react-server-dom-esm/client',
},
},
},
)
pipe(res)
} catch (e) {
console.error(`Failed to SSR: ${e.stack}`)
res.statusCode = 500
res.end(`Failed to SSR: ${e.stack}`)
}
} else {
try {
const rscResponse = await promiseForData

// Forward all headers from the RSC response to the client response
Object.entries(rscResponse.headers).forEach(([header, value]) => {
res.set(header, value)
})

if (req.get('rsc-action')) {
res.set('Content-type', 'text/x-component')
}

rscResponse.on('data', data => {
res.write(data)
res.flush()
})
rscResponse.on('end', () => {
res.end()
})
} catch (e) {
console.error(`Failed to proxy request: ${e.stack}`)
res.statusCode = 500
res.end(`Failed to proxy request: ${e.stack}`)
}
}
})

const server = app.listen(PORT, () => {
console.log(`✅ SSR: http://localhost:${PORT}`)
})

closeWithGrace(async ({ signal, err }) => {
if (err) console.error('Shutting down server due to error', err)
else console.log('Shutting down server due to signal', signal)

await new Promise((resolve, reject) => {
server.close(err => {
if (err) reject(err)
else resolve()
})
})
})
79 changes: 79 additions & 0 deletions exercises/01.exercises/08.solution.hydrate/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createElement as h, Suspense } from 'react'
import { shipDataStorage } from '../server/async-storage.js'
import { ErrorBoundary } from './error-boundary.js'
import { shipFallbackSrc } from './img-utils.js'
import { ShipDetailsPendingTransition } from './ship-details-pending.js'
import { ShipDetails, ShipFallback, ShipError } from './ship-details.js'
import { SearchResults, SearchResultsFallback } from './ship-search-results.js'
import { ShipSearch } from './ship-search.js'

export async function Document() {
return h(
'html',
{ lang: 'en' },
h(
'head',
null,
h('meta', { charSet: 'utf-8' }),
h('meta', {
name: 'viewport',
content: 'width=device-width, initial-scale=1',
}),
h('title', null, 'Super Simple RSC'),
h('link', { rel: 'stylesheet', href: '/style.css' }),
h('link', {
rel: 'shortcut icon',
type: 'image/svg+xml',
href: '/favicon.svg',
}),
),
h('body', null, h('div', { className: 'app-wrapper' }, h(App))),
)
}

function App() {
const { shipId, search } = shipDataStorage.getStore()
return h(
'div',
{ className: 'app' },
h(
ErrorBoundary,
{
fallback: h(
'div',
{ className: 'app-error' },
h('p', null, 'Something went wrong!'),
),
},
h(
Suspense,
{
fallback: h('img', {
style: { maxWidth: 400 },
src: shipFallbackSrc,
}),
},
h(
'div',
{ className: 'search' },
h(ShipSearch, {
search,
results: h(SearchResults, { search }),
fallback: h(SearchResultsFallback),
}),
),
h(
ShipDetailsPendingTransition,
null,
h(
ErrorBoundary,
{ fallback: h(ShipError) },
shipId
? h(Suspense, { fallback: h(ShipFallback) }, h(ShipDetails))
: h('p', null, 'Select a ship from the list to see details'),
),
),
),
),
)
}
Loading

0 comments on commit 4e1c59c

Please sign in to comment.