1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
/*
* This file is a bundler integration between react (react-client/flight), esbuild and server-reason-react.
*
* Similar resources
* **react-server-dom-webpack**
* - https://github.com/facebook/react/blob/5c56b873efb300b4d1afc4ba6f16acf17e4e5800/packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js#L156-L194
* - https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js
*
* Take a look at new’s react-server-dom-parcel https://github.com/facebook/react/pull/31725
*
* What’s possible with esbuild
*
* - RSC server + client https://github.com/jacob-ebey/oneup/blob/main/packages/cli/index.ts
* - https://github.com/jfortunato/esbuild-plugin-manifest/blob/master/src/index.ts
*/
import ReactClientFlight from "@pedrobslisboa/react-client/flight";
const is_debug = false;
const debug = (...args) => {
if (is_debug && process.env.NODE_ENV === "development") {
console.log(...args);
}
};
const ReactFlightClientStreamConfigWeb = {
createStringDecoder() {
return new TextDecoder();
},
readPartialStringChunk(decoder, buffer) {
return decoder.decode(buffer, { stream: true });
},
readFinalStringChunk(decoder, buffer) {
return decoder.decode(buffer);
},
};
const badgeFormat = "%c%s%c ";
// Same badge styling as DevTools.
const badgeStyle =
// We use a fixed background if light-dark is not supported, otherwise
// we use a transparent background.
"background: #e6e6e6;" +
"background: light-dark(rgba(0,0,0,0.1), rgba(255,255,255,0.25));" +
"color: #000000;" +
"color: light-dark(#000000, #ffffff);" +
"border-radius: 2px";
const resetStyle = "";
const pad = " ";
const bind = Function.prototype.bind;
const ReactClientConsoleConfigBrowser = {
bindToConsole(methodName, args, badgeName) {
let offset = 0;
switch (methodName) {
case "dir":
case "dirxml":
case "groupEnd":
case "table": {
// These methods cannot be colorized because they don't take a formatting string.
return bind.apply(console[methodName], [console].concat(args));
}
case "assert": {
// assert takes formatting options as the second argument.
offset = 1;
}
}
const newArgs = args.slice(0);
if (typeof newArgs[offset] === "string") {
newArgs.splice(
offset,
1,
badgeFormat + newArgs[offset],
badgeStyle,
pad + badgeName + pad,
resetStyle
);
} else {
newArgs.splice(
offset,
0,
badgeFormat,
badgeStyle,
pad + badgeName + pad,
resetStyle
);
}
// The "this" binding in the "bind";
newArgs.unshift(console);
return bind.apply(console[methodName], newArgs);
},
};
const ID = 0;
const NAME = 1;
const BUNDLES = 2;
const ReactFlightClientConfigBundlerEsbuild = {
prepareDestinationForModule(moduleLoading, nonce, metadata) {
debug("prepareDestinationForModule", moduleLoading, nonce, metadata);
return;
},
resolveClientReference(bundlerConfig, metadata) {
debug("resolveClientReference", bundlerConfig, metadata);
// Reference is already resolved during the build
return {
type: "ClientComponent",
id: metadata[ID],
name: metadata[NAME],
bundles: metadata[BUNDLES],
};
},
resolveServerReference(bundlerConfig, ref) {
debug("resolveServerReference", bundlerConfig, ref);
return {
type: "ServerFunction",
id: ref,
};
},
preloadModule(metadata) {
debug("preloadModule", metadata);
/* TODO: Does it make sense to preload a module in esbuild? */
return undefined;
},
requireModule(metadata) {
const getModule = (type, id) => {
switch (type) {
case "ServerFunction":
const fn = window.__server_functions_manifest_map[id];
return fn;
case "ClientComponent":
const component = window.__client_manifest_map[id];
return component
}
}
const module = getModule(metadata.type, metadata.id);
if (!module) {
throw new Error(`Could not find module of type ${metadata.type} with id: ${metadata.id}`);
}
return module
},
};
/* TODO: Can we use the real thing, instead of mocks/vendored code here? */
const ReactServerDOMEsbuildConfig = {
...ReactFlightClientStreamConfigWeb,
...ReactClientConsoleConfigBrowser,
...ReactFlightClientConfigBundlerEsbuild,
rendererVersion: "19.0.0",
rendererPackageName: "react-server-dom-esbuild",
usedWithSSR: true,
};
const {
createResponse,
createServerReference: createServerReferenceImpl,
processReply,
getRoot,
reportGlobalError,
processBinaryChunk,
close,
} = ReactClientFlight(ReactServerDOMEsbuildConfig);
function startReadingFromStream(response, stream) {
const reader = stream.getReader();
function progress({ done, value }) {
if (done) {
close(response);
return;
}
const buffer = value;
processBinaryChunk(response, buffer);
return reader.read().then(progress).catch(error);
}
function error(e) {
reportGlobalError(response, e);
}
reader.read().then(progress).catch(error);
}
function callCurrentServerCallback(callServer) {
return function (id, args) {
if (!callServer) {
throw new Error(
"No server callback has been registered. Call setServerCallback to register one."
);
}
return callServer(id, args);
};
}
export function createFromReadableStream(stream, options) {
const response = createResponseFromOptions(options);
startReadingFromStream(response, stream);
return getRoot(response);
}
function createResponseFromOptions(options) {
let response = createResponse(
// [QUESTION] Should we have for client components the same as we have for server functions?
null, // bundlerConfig
// serverFunctionsConfig, this is the manifest that can contain configs related to server functions
// Unfortunatelly, react requires it to not be null, to run resolveServerReference
{},
null, // moduleLoading
callCurrentServerCallback(options ? options.callServer : undefined),
undefined, // encodeFormAction
undefined, // nonce
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
false /* __DEV__ ? (options ? options.replayConsoleLogs !== false : true) */,
undefined /* __DEV__ && options && options.environmentName
? options.environmentName
: undefined */
);
return response;
}
export function createFromFetch(promise, options) {
const response = createResponseFromOptions(options);
promise.then(
function (r) {
startReadingFromStream(response, r.body);
},
function (e) {
reportGlobalError(response, e);
}
);
return getRoot(response);
}
export const createServerReference = createServerReferenceImpl;
export const encodeReply = (
value,
options = { temporaryReferences: undefined, signal: undefined }
) => {
return new Promise((resolve, reject) => {
const abort = processReply(
value,
"",
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
resolve,
reject
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(signal.reason);
} else {
const listener = () => {
abort(signal.reason);
signal.removeEventListener("abort", listener);
};
signal.addEventListener("abort", listener);
}
}
});
};
|