A tool for people curious about the React Server Components protocol
rscexplorer.dev/
rsc
react
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}