-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e301ef9
commit 4e1c59c
Showing
115 changed files
with
604 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Hydrate |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
...ses/10.solution.module-graph/package.json → ...ses/07.solution.module-graph/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Hydrate |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
...ises/10.problem.module-graph/package.json → ...xercises/08.solution.hydrate/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions
3
exercises/01.exercises/08.solution.hydrate/server/register-rsc-loader.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
23
exercises/01.exercises/08.solution.hydrate/server/rsc-loader.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} |
File renamed without changes.
172 changes: 172 additions & 0 deletions
172
exercises/01.exercises/08.solution.hydrate/server/ssr.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'), | ||
), | ||
), | ||
), | ||
), | ||
) | ||
} |
Oops, something went wrong.