Skip to content

Commit

Permalink
RSC: Decode RSF args from request (#9157)
Browse files Browse the repository at this point in the history
Add support for passing arguments to server actions
  • Loading branch information
Tobbe authored Sep 12, 2023
1 parent c46bb67 commit 9257e39
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 35 deletions.
2 changes: 2 additions & 0 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"@vitejs/plugin-react": "4.0.4",
"acorn-loose": "8.3.0",
"buffer": "6.0.3",
"busboy": "^1.6.0",
"core-js": "3.32.2",
"dotenv-defaults": "5.0.2",
"express": "4.18.2",
Expand All @@ -83,6 +84,7 @@
},
"devDependencies": {
"@babel/cli": "7.22.10",
"@types/busboy": "^1",
"@types/express": "4",
"@types/react": "18.2.14",
"@types/yargs-parser": "21.0.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/vite/src/lib/StatusError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ export class StatusError extends Error {
super(message)
}
}

// TODO (RSC): Do we need this? Can we just check instanceof StatusError?
export const hasStatusCode = (x: unknown): x is { statusCode: number } =>
typeof (x as any)?.statusCode === 'number'
91 changes: 56 additions & 35 deletions packages/vite/src/runRscFeServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@
import fs from 'fs/promises'
import path from 'path'

import busboy from 'busboy'
// @ts-expect-error We will remove dotenv-defaults from this package anyway
import { config as loadDotEnv } from 'dotenv-defaults'
import express from 'express'
import { createProxyMiddleware } from 'http-proxy-middleware'
import isbot from 'isbot'
import RSDWServer from 'react-server-dom-webpack/server.node.unbundled'
import type { Manifest as ViteBuildManifest } from 'vite'

import { getConfig, getPaths } from '@redwoodjs/project-config'

import { hasStatusCode } from './lib/StatusError'
import { registerFwGlobals } from './streaming/registerGlobals'
import { renderRSC, setClientEntries } from './waku-lib/rsc-handler-worker'

const { decodeReply, decodeReplyFromBusboy } = RSDWServer

/**
* TODO (STREAMING)
* We have this server in the vite package only temporarily.
Expand Down Expand Up @@ -120,62 +125,78 @@ export async function runFeServer() {
console.log('basePath', basePath)
console.log('req.originalUrl', req.originalUrl, 'req.url', req.url)
console.log('req.headers.host', req.headers.host)

const url = new URL(req.originalUrl || '', 'http://' + req.headers.host)
let rscId: string | undefined
let props = {}
let rsfId: string | undefined
const args: unknown[] = []
let args: unknown[] = []

console.log('url.pathname', url.pathname)

if (url.pathname.startsWith(basePath)) {
const index = url.pathname.lastIndexOf('/')
const params = new URLSearchParams(url.pathname.slice(index + 1))
rscId = url.pathname.slice(basePath.length, index)
rsfId = params.get('action_id') || undefined

console.log('rscId', rscId)
const params = new URLSearchParams(url.pathname.slice(index + 1))
console.log('rsfId', rsfId)

if (rscId && rscId !== '_') {
res.setHeader('Content-Type', 'text/x-component')
props = JSON.parse(params.get('props') || '{}')
} else {
rscId = undefined
}
rsfId = params.get('action_id') || undefined

if (rsfId) {
console.warn('RSF is not supported yet')
console.warn('RSF is not supported yet')
console.warn('RSF is not supported yet')
// if (req.headers["content-type"]?.startsWith("multipart/form-data")) {
// const bb = busboy({ headers: req.headers });
// const reply = decodeReplyFromBusboy(bb);
// req.pipe(bb);
// args = await reply;
// } else {
// let body = "";
// for await (const chunk of req) {
// body += chunk;
// }
// if (body) {
// args = await decodeReply(body);
// }
// }
if (req.headers['content-type']?.startsWith('multipart/form-data')) {
const bb = busboy({ headers: req.headers })
const reply = decodeReplyFromBusboy(bb)

req.pipe(bb)
args = await reply
} else {
let body = ''

for await (const chunk of req) {
body += chunk
}

if (body) {
args = await decodeReply(body)
}
}
}
}

if (rscId || rsfId) {
const pipeable = await renderRSC({ rscId, props, rsfId, args })

// TODO handle errors

// pipeable.on('error', (err) => {
// console.info('Cannot render RSC', err)
// res.statusCode = 500
// if (options.mode === 'development') {
// res.end(String(err))
// } else {
// res.end()
// }
// })
pipeable.pipe(res)
return
const handleError = (err: unknown) => {
if (hasStatusCode(err)) {
res.statusCode = err.statusCode
} else {
console.info('Cannot render RSC', err)
res.statusCode = 500
}

res.end(String(err))
// TODO (RSC): When we have `yarn rw dev` support we should do this:
// if (options.command === 'dev') {
// res.end(String(err))
// } else {
// res.end()
// }
}

try {
const pipeable = await renderRSC({ rscId, props, rsfId, args })
// TODO (RSC): See if we can/need to do more error handling here
// pipeable.on(handleError)
pipeable.pipe(res)
} catch (e) {
handleError(e)
}
}
})

Expand Down
11 changes: 11 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9144,12 +9144,14 @@ __metadata:
"@redwoodjs/project-config": 6.0.7
"@redwoodjs/web": 6.0.7
"@swc/core": 1.3.60
"@types/busboy": ^1
"@types/express": 4
"@types/react": 18.2.14
"@types/yargs-parser": 21.0.0
"@vitejs/plugin-react": 4.0.4
acorn-loose: 8.3.0
buffer: 6.0.3
busboy: ^1.6.0
core-js: 3.32.2
dotenv-defaults: 5.0.2
express: 4.18.2
Expand Down Expand Up @@ -11136,6 +11138,15 @@ __metadata:
languageName: node
linkType: hard

"@types/busboy@npm:^1":
version: 1.5.0
resolution: "@types/busboy@npm:1.5.0"
dependencies:
"@types/node": "*"
checksum: 5ba425839c89ba70f504047e4c85068f07309d47c35407645a8b29c2b68bd85bf93b1ca11ed431657c63f602d465b18918f704ed6bf551060ae53ce83d357800
languageName: node
linkType: hard

"@types/cacheable-request@npm:^6.0.1":
version: 6.0.3
resolution: "@types/cacheable-request@npm:6.0.3"
Expand Down

0 comments on commit 9257e39

Please sign in to comment.