Skip to content

Commit 37906d4

Browse files
authoredJan 27, 2025··
[Flight Parcel] Pass import maps through client references (#32132)
Corresponding Parcel PR: parcel-bundler/parcel#10073 Parcel avoids [cascading cache invalidation](https://philipwalton.com/articles/cascading-cache-invalidation/) by injecting a bundle manifest containing a mapping of stable bundle ids to hashed URLs. When using an HTML entry point, this is done (as of the above PR) via a native import map. This means that if a bundle's hash changes, only that bundle will be invalidated (plus the HTML itself which typically has a short caching policy), not any other bundles that reference it. For RSCs, we cannot currently use native import maps because of client side navigations, where a new HTML file is not requested. Eventually, multiple `<script type="importmap">` elements will be supported (whatwg/html#10528) ([coming Chrome 133](https://chromestatus.com/feature/5121916248260608)), at which point React could potentially inject them. In the meantime, I've added some APIs to Parcel to polyfill this. With this change, an import map can be sent along with a client reference, containing a mapping for any dynamic imports and URL dependencies (e.g. images) that are referenced by the JS bundles. On the client, the import map is extended with these new mappings prior to executing the referenced bundles. This preserves the caching advantages described above while supporting client navigations.
1 parent d676c04 commit 37906d4

File tree

5 files changed

+32
-4
lines changed

5 files changed

+32
-4
lines changed
 

‎packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type ClientReference<T> = {
2222
$$id: string,
2323
$$name: string,
2424
$$bundles: Array<string>,
25+
$$importMap?: ?{[string]: string},
2526
};
2627

2728
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
@@ -39,12 +40,14 @@ export function createClientReference<T>(
3940
id: string,
4041
exportName: string,
4142
bundles: Array<string>,
43+
importMap?: ?{[string]: string},
4244
): ClientReference<T> {
4345
return {
4446
$$typeof: CLIENT_REFERENCE_TAG,
4547
$$id: id,
4648
$$name: exportName,
4749
$$bundles: bundles,
50+
$$importMap: importMap,
4851
};
4952
}
5053

‎packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import type {Thenable} from 'shared/ReactTypes';
1111

1212
import type {ImportMetadata} from '../shared/ReactFlightImportMetadata';
1313

14-
import {ID, NAME, BUNDLES} from '../shared/ReactFlightImportMetadata';
14+
import {
15+
ID,
16+
NAME,
17+
BUNDLES,
18+
IMPORT_MAP,
19+
} from '../shared/ReactFlightImportMetadata';
1520
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
1621

1722
export type ServerManifest = {
@@ -60,9 +65,14 @@ export function resolveServerReference<T>(
6065
export function preloadModule<T>(
6166
metadata: ClientReference<T>,
6267
): null | Thenable<any> {
68+
if (metadata[IMPORT_MAP]) {
69+
parcelRequire.extendImportMap(metadata[IMPORT_MAP]);
70+
}
71+
6372
if (metadata[BUNDLES].length === 0) {
6473
return null;
6574
}
75+
6676
return Promise.all(metadata[BUNDLES].map(url => parcelRequire.load(url)));
6777
}
6878

‎packages/react-server-dom-parcel/src/server/ReactFlightServerConfigParcelBundler.js

+9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ export function resolveClientReferenceMetadata<T>(
3737
config: ClientManifest,
3838
clientReference: ClientReference<T>,
3939
): ClientReferenceMetadata {
40+
if (clientReference.$$importMap) {
41+
return [
42+
clientReference.$$id,
43+
clientReference.$$name,
44+
clientReference.$$bundles,
45+
clientReference.$$importMap,
46+
];
47+
}
48+
4049
return [
4150
clientReference.$$id,
4251
clientReference.$$name,

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
// This is the parsed shape of the wire format which is why it is
1111
// condensed to only the essentialy information
1212
export type ImportMetadata = [
13-
/* id */ string,
14-
/* name */ string,
15-
/* bundles */ Array<string>,
13+
// eslint does not understand Flow tuple syntax.
14+
/* eslint-disable */
15+
id: string,
16+
name: string,
17+
bundles: Array<string>,
18+
importMap?: {[string]: string},
19+
/* eslint-enable */
1620
];
1721

1822
export const ID = 0;
1923
export const NAME = 1;
2024
export const BUNDLES = 2;
25+
export const IMPORT_MAP = 3;

‎scripts/flow/environment.js

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ declare const __turbopack_require__: ((id: string) => any) & {
106106
declare var parcelRequire: {
107107
(id: string): any,
108108
load: (url: string) => Promise<mixed>,
109+
extendImportMap: (importMap: {[string]: string}) => void,
109110
meta: {
110111
publicUrl: string,
111112
},

0 commit comments

Comments
 (0)
Please sign in to comment.