Skip to content

Commit af24e98

Browse files
committed
[Flight] Standardize ClientReferenceMetadata tuple type
Previously, `ClientReferenceMetadata` was declared as an opaque type in the Flight server config (`ReactFlightServerConfigBundlerCustom.js`), with each bundler defining its own tuple shape. Webpack, Turbopack, and Unbundled used `[id, chunks, name, async?]` while Parcel used `[id, name, bundles, importMap?]`, placing the chunks array at different indices. This made it impossible for `ReactFlightServer.js` to access the chunks array without bundler-specific accessor functions. This change standardizes all bundlers on a common shape where `id` is at index 0, `name` is at index 1, and `chunks` is at index 2. A bundler- specific extra like the async flag or Parcel's `importMap` can go at index 3. The `ClientReferenceMetadata` type is changed from an opaque type to a concrete union type, so `ReactFlightServer.js` can now directly access `metadata[2]` to get the chunks array with proper Flow typing. This only affects the internal wire format tuple, not the `ClientManifest` that frameworks pass into Flight. The manifest entry shape (`{id, chunks, name, async?}`) remains unchanged. The motivation is to enable deduplicating individual chunk URLs across client references in the RSC stream. With a known, non-opaque type, `emitImportChunk` can extract, deduplicate, and replace chunk entries without needing bundler-specific callback functions or accessor methods.
1 parent 80b1cab commit af24e98

13 files changed

Lines changed: 45 additions & 35 deletions

File tree

packages/react-noop-renderer/src/ReactNoopFlightClient.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,14 @@ const {createResponse, createStreamState, processBinaryChunk, getRoot, close} =
3737
readFinalStringChunk(decoder: TextDecoder, buffer: Uint8Array): string {
3838
return decoder.decode(buffer);
3939
},
40-
resolveClientReference(bundlerConfig: null, idx: string) {
41-
return idx;
40+
resolveClientReference(bundlerConfig: null, metadata: [string, string]) {
41+
return metadata[0];
4242
},
43-
prepareDestinationForModule(moduleLoading: null, metadata: string) {},
43+
prepareDestinationForModule(
44+
moduleLoading: null,
45+
nonce: ?string,
46+
metadata: [string, string],
47+
) {},
4448
preloadModule(idx: string) {},
4549
requireModule(idx: string) {
4650
return readModule(idx);

packages/react-noop-renderer/src/ReactNoopFlightServer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const ReactNoopFlightServer = ReactFlightServer({
6363
config: void,
6464
reference: {$$typeof: symbol, value: any},
6565
) {
66-
return saveModule(reference.value);
66+
return [saveModule(reference.value), '*'];
6767
},
6868
});
6969

packages/react-server-dom-parcel/src/shared/ReactFlightImportMetadata.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type ImportMetadata = [
1515
id: string,
1616
name: string,
1717
bundles: Array<string>,
18-
importMap?: {[string]: string},
18+
importMap?: {[string]: string} | void,
1919
/* eslint-enable */
2020
];
2121

packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ export function resolveClientReference<T>(
9595
if (isAsyncImport(metadata)) {
9696
return [
9797
resolvedModuleData.id,
98-
resolvedModuleData.chunks,
9998
name,
99+
resolvedModuleData.chunks,
100100
1 /* async */,
101101
];
102102
} else {
103-
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
103+
return [resolvedModuleData.id, name, resolvedModuleData.chunks];
104104
}
105105
}
106106
return metadata;
@@ -142,12 +142,12 @@ export function resolveServerReference<T>(
142142
// manifest.
143143
return [
144144
resolvedModuleData.id,
145-
resolvedModuleData.chunks,
146145
name,
146+
resolvedModuleData.chunks,
147147
1 /* async */,
148148
];
149149
}
150-
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
150+
return [resolvedModuleData.id, name, resolvedModuleData.chunks];
151151
}
152152

153153
function requireAsyncModule(id: string): null | Thenable<any> {

packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ export function resolveClientReferenceMetadata<T>(
8080
);
8181
}
8282
if (resolvedModuleData.async === true || clientReference.$$async === true) {
83-
return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
83+
return [resolvedModuleData.id, name, resolvedModuleData.chunks, 1];
8484
} else {
85-
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
85+
return [resolvedModuleData.id, name, resolvedModuleData.chunks];
8686
}
8787
}
8888

packages/react-server-dom-turbopack/src/shared/ReactFlightImportMetadata.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ export type ImportManifestEntry = {
2020
export type ImportMetadata =
2121
| [
2222
/* id */ string,
23-
/* chunk filenames */ Array<string>,
2423
/* name */ string,
24+
/* chunk filenames */ Array<string>,
2525
/* async */ 1,
2626
]
27-
| [/* id */ string, /* chunk filenames */ Array<string>, /* name */ string];
27+
| [/* id */ string, /* name */ string, /* chunk filenames */ Array<string>];
2828

2929
export const ID = 0;
30-
export const CHUNKS = 1;
31-
export const NAME = 2;
30+
export const NAME = 1;
31+
export const CHUNKS = 2;
3232
// export const ASYNC = 3;
3333

3434
// This logic is correct because currently only include the 4th tuple member

packages/react-server-dom-unbundled/src/server/ReactFlightServerConfigUnbundledBundler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ export function resolveClientReferenceMetadata<T>(
8080
);
8181
}
8282
if (resolvedModuleData.async === true || clientReference.$$async === true) {
83-
return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
83+
return [resolvedModuleData.id, name, resolvedModuleData.chunks, 1];
8484
} else {
85-
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
85+
return [resolvedModuleData.id, name, resolvedModuleData.chunks];
8686
}
8787
}
8888

packages/react-server-dom-unbundled/src/shared/ReactFlightImportMetadata.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,23 @@ export type ImportManifestEntry = {
1616
};
1717

1818
// This is the parsed shape of the wire format which is why it is
19-
// condensed to only the essentialy information
19+
// condensed to only the essentially information
2020
export type ImportMetadata =
2121
| [
2222
/* id */ string,
23-
/* chunks id/filename pairs, double indexed */ Array<string>,
2423
/* name */ string,
24+
/* chunks id/filename pairs, double indexed */ Array<string>,
2525
/* async */ 1,
2626
]
2727
| [
2828
/* id */ string,
29-
/* chunks id/filename pairs, double indexed */ Array<string>,
3029
/* name */ string,
30+
/* chunks id/filename pairs, double indexed */ Array<string>,
3131
];
3232

3333
export const ID = 0;
34-
export const CHUNKS = 1;
35-
export const NAME = 2;
34+
export const NAME = 1;
35+
export const CHUNKS = 2;
3636
// export const ASYNC = 3;
3737

3838
// This logic is correct because currently only include the 4th tuple member

packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ export function resolveClientReference<T>(
102102
if (isAsyncImport(metadata)) {
103103
return [
104104
resolvedModuleData.id,
105-
resolvedModuleData.chunks,
106105
name,
106+
resolvedModuleData.chunks,
107107
1 /* async */,
108108
];
109109
} else {
110-
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
110+
return [resolvedModuleData.id, name, resolvedModuleData.chunks];
111111
}
112112
}
113113
return metadata;
@@ -149,12 +149,12 @@ export function resolveServerReference<T>(
149149
// manifest.
150150
return [
151151
resolvedModuleData.id,
152-
resolvedModuleData.chunks,
153152
name,
153+
resolvedModuleData.chunks,
154154
1 /* async */,
155155
];
156156
}
157-
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
157+
return [resolvedModuleData.id, name, resolvedModuleData.chunks];
158158
}
159159

160160
// The chunk cache contains all the chunks we've preloaded so far.

packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ export function resolveClientReferenceMetadata<T>(
8080
);
8181
}
8282
if (resolvedModuleData.async === true || clientReference.$$async === true) {
83-
return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
83+
return [resolvedModuleData.id, name, resolvedModuleData.chunks, 1];
8484
} else {
85-
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
85+
return [resolvedModuleData.id, name, resolvedModuleData.chunks];
8686
}
8787
}
8888

0 commit comments

Comments
 (0)