A tool for people curious about the React Server Components protocol rscexplorer.dev/
rsc react
at main 106 lines 3.1 kB view raw
1import workerUrl from "../server/worker-server.ts?rolldown-worker"; 2import type { Response, EncodedArgs, Deploy, Render, CallAction } from "../server/worker-server.ts"; 3import type { ClientManifest } from "../shared/compiler.ts"; 4import { polyfillReady } from "../shared/polyfill.ts"; 5 6export type { EncodedArgs, ClientManifest }; 7 8export function encodeArgs(encoded: FormData | string): EncodedArgs { 9 if (encoded instanceof FormData) { 10 return { 11 type: "formdata", 12 data: new URLSearchParams(encoded as unknown as Record<string, string>).toString(), 13 }; 14 } 15 return { type: "string", data: encoded }; 16} 17 18export class WorkerClient { 19 private worker: Worker; 20 private requests = new Map<string, ReadableStreamDefaultController<Uint8Array>>(); 21 private readyPromise: Promise<void>; 22 private readyResolve!: () => void; 23 24 constructor(signal: AbortSignal) { 25 this.worker = new Worker(workerUrl); 26 27 const dispose = (reason: unknown) => { 28 for (const controller of this.requests.values()) { 29 controller.error(reason); 30 } 31 this.worker.terminate(); 32 this.requests.clear(); 33 }; 34 35 this.readyPromise = new Promise((resolve, reject) => { 36 this.readyResolve = resolve; 37 signal.addEventListener("abort", () => { 38 reject(signal.reason); 39 dispose(signal.reason); 40 }); 41 }); 42 this.worker.onmessage = (msg) => this.handleMessage(msg); 43 this.worker.onerror = (e) => dispose(e.error); 44 } 45 46 private handleMessage(event: MessageEvent<Response>): void { 47 const msg = event.data; 48 49 if (msg.type === "ready") { 50 this.readyResolve(); 51 return; 52 } 53 54 const controller = this.requests.get(msg.requestId); 55 if (!controller) throw new Error(`Unknown request: ${msg.requestId}`); 56 57 switch (msg.type) { 58 case "next": 59 controller.enqueue(msg.value); 60 break; 61 62 case "done": 63 controller.close(); 64 this.requests.delete(msg.requestId); 65 break; 66 67 case "throw": { 68 const err = new Error(msg.error); 69 if (msg.stack) { 70 err.stack = msg.stack; 71 } 72 controller.error(err); 73 this.requests.delete(msg.requestId); 74 break; 75 } 76 } 77 } 78 79 private nextRequestId = 0; 80 81 private async request(body: Record<string, unknown>): Promise<ReadableStream<Uint8Array>> { 82 await Promise.all([this.readyPromise, polyfillReady]); 83 const requestId = String(this.nextRequestId++); 84 let controller!: ReadableStreamDefaultController<Uint8Array>; 85 const stream = new ReadableStream<Uint8Array>({ 86 start: (c) => { 87 controller = c; 88 }, 89 }); 90 this.requests.set(requestId, controller); 91 this.worker.postMessage({ ...body, requestId }); 92 return stream; 93 } 94 95 deploy(...args: Parameters<Deploy>): Promise<ReturnType<Deploy>> { 96 return this.request({ method: "deploy", args }); 97 } 98 99 render(...args: Parameters<Render>): Promise<ReturnType<Render>> { 100 return this.request({ method: "render", args }); 101 } 102 103 callAction(...args: Parameters<CallAction>): ReturnType<CallAction> { 104 return this.request({ method: "action", args }); 105 } 106}