1// Server dependencies.2import express, {3 type Request as ExpressRequest,4 type Response as ExpressResponse,5} from 'express';6import {Readable} from 'node:stream';7import type {ReadableStream as NodeReadableStream} from 'stream/web';8import {9 renderToReadableStream,10 loadServerAction,11 decodeReply,12 decodeAction,13} from 'react-server-dom-parcel/server.edge';14import {injectRSCPayload} from 'rsc-html-stream/server';1516// Client dependencies, used for SSR.17// These must run in the same environment as client components (e.g. same instance of React).18import {createFromReadableStream} from 'react-server-dom-parcel/client.edge' with {env: 'react-client'};19import {renderToReadableStream as renderHTMLToReadableStream} from 'react-dom/server.edge' with {env: 'react-client'};20import ReactClient, {ReactElement} from 'react' with {env: 'react-client'};2122// Page components. These must have "use server-entry" so they are treated as code splitting entry points.23import {Todos} from './Todos';2425const app = express();2627app.use(function (req, res, next) {28 res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,POST');29 res.setHeader('Access-Control-Allow-Origin', '*');30 res.setHeader('Access-Control-Allow-Headers', 'rsc-action');31 next();32});3334app.use(express.static('dist'));3536app.get('/', async (req, res) => {37 await render(req, res, <Todos />);38});3940app.post('/', async (req, res) => {41 await handleAction(req, res, <Todos />);42});4344app.get('/todos/:id', async (req, res) => {45 await render(req, res, <Todos id={Number(req.params.id)} />);46});4748app.post('/todos/:id', async (req, res) => {49 await handleAction(req, res, <Todos id={Number(req.params.id)} />);50});5152async function render(53 req: ExpressRequest,54 res: ExpressResponse,55 component: ReactElement,56 actionResult?: any,57) {58 // Render RSC payload.59 let root: any = component;60 if (actionResult) {61 root = {result: actionResult, root};62 }63 let stream = renderToReadableStream(root);64 if (req.accepts('text/html')) {65 res.setHeader('Content-Type', 'text/html');6667 // Use client react to render the RSC payload to HTML.68 let [s1, s2] = stream.tee();69 let data: Promise<ReactElement>;70 function Content() {71 data ??= createFromReadableStream<ReactElement>(s1);72 return ReactClient.use(data);73 }7475 let htmlStream = await renderHTMLToReadableStream(<Content />, {76 bootstrapScriptContent: (Todos as any).bootstrapScript,77 });78 let response = htmlStream.pipeThrough(injectRSCPayload(s2));79 Readable.fromWeb(response as NodeReadableStream).pipe(res);80 } else {81 res.set('Content-Type', 'text/x-component');82 Readable.fromWeb(stream as NodeReadableStream).pipe(res);83 }84}8586// Handle server actions.87async function handleAction(88 req: ExpressRequest,89 res: ExpressResponse,90 component: ReactElement,91) {92 let id = req.get('rsc-action-id');93 let request = new Request('http://localhost' + req.url, {94 method: 'POST',95 headers: req.headers as any,96 body: Readable.toWeb(req) as ReadableStream,97 // @ts-ignore98 duplex: 'half',99 });100101 if (id) {102 let action = await loadServerAction(id);103 let body = req.is('multipart/form-data')104 ? await request.formData()105 : await request.text();106 let args = await decodeReply<any[]>(body);107 let result = action.apply(null, args);108 try {109 // Wait for any mutations110 await result;111 } catch (x) {112 // We handle the error on the client113 }114115 await render(req, res, component, result);116 } else {117 // Form submitted by browser (progressive enhancement).118 let formData = await request.formData();119 let action = await decodeAction(formData);120 try {121 // Wait for any mutations122 await action();123 } catch (err) {124 // TODO render error page?125 }126 await render(req, res, component);127 }128}129130let server = app.listen(3001);131console.log('Server listening on port 3001');132133// Restart the server when it changes.134if (module.hot) {135 module.hot.dispose(() => {136 server.close();137 });138139 module.hot.accept();140}
Findings
✓ No findings reported for this file.