-
Notifications
You must be signed in to change notification settings - Fork 995
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(rsc): Refactor: Split rscWorker into worker and renderer (#11452)
- Loading branch information
Showing
4 changed files
with
202 additions
and
202 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import path from 'node:path' | ||
import type { ReadableStream } from 'node:stream/web' | ||
|
||
import { createElement } from 'react' | ||
|
||
import { renderToReadableStream } from 'react-server-dom-webpack/server.edge' | ||
|
||
import type { ServerAuthState } from '@redwoodjs/auth/dist/AuthProvider/ServerAuthProvider.js' | ||
import { getPaths } from '@redwoodjs/project-config' | ||
|
||
import type { RscFetchProps } from '../../../router/src/rsc/ClientRouter.tsx' | ||
import { getEntriesFromDist } from '../lib/entries.js' | ||
import { StatusError } from '../lib/StatusError.js' | ||
|
||
export type RenderInput = { | ||
rscId?: string | undefined | ||
props: RscFetchProps | Record<string, unknown> | ||
rsaId?: string | undefined | ||
args?: unknown[] | undefined | ||
serverState: { | ||
headersInit: Record<string, string> | ||
fullUrl: string | ||
serverAuthState: ServerAuthState | ||
} | ||
} | ||
|
||
let absoluteClientEntries: Record<string, string> = {} | ||
|
||
async function loadServerFile(filePath: string) { | ||
console.log('rscRenderer.ts loadServerFile filePath', filePath) | ||
return import(`file://${filePath}`) | ||
} | ||
|
||
const getRoutesComponent: any = async () => { | ||
const serverEntries = await getEntriesFromDist() | ||
console.log('rscRenderer.ts serverEntries', serverEntries) | ||
|
||
const routesPath = path.join( | ||
getPaths().web.distRsc, | ||
serverEntries['__rwjs__Routes'], | ||
) | ||
|
||
if (!routesPath) { | ||
throw new StatusError('No entry found for __rwjs__Routes', 404) | ||
} | ||
|
||
const routes = await loadServerFile(routesPath) | ||
|
||
return routes.default | ||
} | ||
|
||
export async function setClientEntries(): Promise<void> { | ||
const entriesFile = getPaths().web.distRscEntries | ||
console.log('setClientEntries :: entriesFile', entriesFile) | ||
const { clientEntries } = await loadServerFile(entriesFile) | ||
console.log('setClientEntries :: clientEntries', clientEntries) | ||
if (!clientEntries) { | ||
throw new Error('Failed to load clientEntries') | ||
} | ||
const baseDir = path.dirname(entriesFile) | ||
|
||
// Convert to absolute paths | ||
absoluteClientEntries = Object.fromEntries( | ||
Object.entries(clientEntries).map(([key, val]) => { | ||
let fullKey = path.join(baseDir, key) | ||
|
||
if (process.platform === 'win32') { | ||
fullKey = fullKey.replaceAll('\\', '/') | ||
} | ||
|
||
return [fullKey, '/' + val] | ||
}), | ||
) | ||
|
||
console.log( | ||
'setClientEntries :: absoluteClientEntries', | ||
absoluteClientEntries, | ||
) | ||
} | ||
|
||
function getBundlerConfig() { | ||
// TODO (RSC): Try removing the proxy here and see if it's really necessary. | ||
// Looks like it'd work to just have a regular object with a getter. | ||
// Remove the proxy and see what breaks. | ||
const bundlerConfig = new Proxy( | ||
{}, | ||
{ | ||
get(_target, encodedId: string) { | ||
console.log('Proxy get encodedId', encodedId) | ||
const [filePath, name] = encodedId.split('#') as [string, string] | ||
// filePath /Users/tobbe/dev/waku/examples/01_counter/dist/assets/rsc0.js | ||
// name Counter | ||
|
||
const filePathSlash = filePath.replaceAll('\\', '/') | ||
const id = absoluteClientEntries[filePathSlash] | ||
|
||
console.log('absoluteClientEntries', absoluteClientEntries) | ||
console.log('filePath', filePathSlash) | ||
|
||
if (!id) { | ||
throw new Error('No client entry found for ' + filePathSlash) | ||
} | ||
|
||
console.log('rscRenderer proxy id', id) | ||
// id /assets/rsc0-beb48afe.js | ||
return { id, chunks: [id], name, async: true } | ||
}, | ||
}, | ||
) | ||
|
||
return bundlerConfig | ||
} | ||
|
||
export async function renderRsc(input: RenderInput): Promise<ReadableStream> { | ||
if (input.rsaId || !input.args) { | ||
throw new Error( | ||
"Unexpected input. Can't request both RSCs and execute RSAs at the same time.", | ||
) | ||
} | ||
|
||
if (!input.rscId || !input.props) { | ||
throw new Error('Unexpected input. Missing rscId or props.') | ||
} | ||
|
||
console.log('renderRsc input', input) | ||
|
||
const serverRoutes = await getRoutesComponent() | ||
// TODO (RSC): Should this have the same shape as for handleRsa? | ||
const model = createElement(serverRoutes, input.props) | ||
|
||
console.log('rscRenderer.ts renderRsc renderRsc props', input.props) | ||
console.log('rscRenderer.ts renderRsc model', model) | ||
|
||
return renderToReadableStream(model, getBundlerConfig()) | ||
// TODO (RSC): We used to transform() the stream here to remove | ||
// "prefixToRemove", which was the common base path to all filenames. We | ||
// then added it back in handleRsa with a simple | ||
// `path.join(config.root, fileId)`. I removed all of that for now to | ||
// simplify the code. But if we wanted to add it back in the future to save | ||
// some bytes in all the Flight data we could. | ||
} | ||
|
||
interface SerializedFormData { | ||
__formData__: boolean | ||
state: Record<string, string | string[]> | ||
} | ||
|
||
function isSerializedFormData(data?: unknown): data is SerializedFormData { | ||
return !!data && (data as SerializedFormData)?.__formData__ | ||
} | ||
|
||
export async function executeRsa(input: RenderInput): Promise<ReadableStream> { | ||
console.log('handleRsa input', input) | ||
|
||
if (!input.rsaId || !input.args) { | ||
throw new Error('Unexpected input') | ||
} | ||
|
||
const [fileName, actionName] = input.rsaId.split('#') | ||
console.log('Server Action fileName', fileName, 'actionName', actionName) | ||
const module = await loadServerFile(fileName) | ||
|
||
if (isSerializedFormData(input.args[0])) { | ||
const formData = new FormData() | ||
|
||
Object.entries(input.args[0].state).forEach(([key, value]) => { | ||
if (Array.isArray(value)) { | ||
value.forEach((v) => { | ||
formData.append(key, v) | ||
}) | ||
} else { | ||
formData.append(key, value) | ||
} | ||
}) | ||
|
||
input.args[0] = formData | ||
} | ||
|
||
const method = module[actionName] || module | ||
console.log('rscRenderer.ts method', method) | ||
console.log('rscRenderer.ts args', ...input.args) | ||
|
||
const data = await method(...input.args) | ||
console.log('rscRenderer.ts rsa return data', data) | ||
|
||
const serverRoutes = await getRoutesComponent() | ||
console.log('rscRenderer.ts handleRsa serverRoutes', serverRoutes) | ||
const model = { | ||
Routes: createElement(serverRoutes, { | ||
location: { pathname: '/', search: '' }, | ||
}), | ||
__rwjs__rsa_data: data, | ||
} | ||
console.log('rscRenderer.ts handleRsa model', model) | ||
|
||
return renderToReadableStream(model, getBundlerConfig()) | ||
} |
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
Oops, something went wrong.