Skip to content

Commit

Permalink
[Flight Parcel] Pass import maps through client references (#32132)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
devongovett authored Jan 27, 2025
1 parent d676c04 commit 37906d4
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type ClientReference<T> = {
$$id: string,
$$name: string,
$$bundles: Array<string>,
$$importMap?: ?{[string]: string},
};

const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
Expand All @@ -39,12 +40,14 @@ export function createClientReference<T>(
id: string,
exportName: string,
bundles: Array<string>,
importMap?: ?{[string]: string},
): ClientReference<T> {
return {
$$typeof: CLIENT_REFERENCE_TAG,
$$id: id,
$$name: exportName,
$$bundles: bundles,
$$importMap: importMap,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import type {Thenable} from 'shared/ReactTypes';

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

import {ID, NAME, BUNDLES} from '../shared/ReactFlightImportMetadata';
import {
ID,
NAME,
BUNDLES,
IMPORT_MAP,
} from '../shared/ReactFlightImportMetadata';
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';

export type ServerManifest = {
Expand Down Expand Up @@ -60,9 +65,14 @@ export function resolveServerReference<T>(
export function preloadModule<T>(
metadata: ClientReference<T>,
): null | Thenable<any> {
if (metadata[IMPORT_MAP]) {
parcelRequire.extendImportMap(metadata[IMPORT_MAP]);
}

if (metadata[BUNDLES].length === 0) {
return null;
}

return Promise.all(metadata[BUNDLES].map(url => parcelRequire.load(url)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ export function resolveClientReferenceMetadata<T>(
config: ClientManifest,
clientReference: ClientReference<T>,
): ClientReferenceMetadata {
if (clientReference.$$importMap) {
return [
clientReference.$$id,
clientReference.$$name,
clientReference.$$bundles,
clientReference.$$importMap,
];
}

return [
clientReference.$$id,
clientReference.$$name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
// This is the parsed shape of the wire format which is why it is
// condensed to only the essentialy information
export type ImportMetadata = [
/* id */ string,
/* name */ string,
/* bundles */ Array<string>,
// eslint does not understand Flow tuple syntax.
/* eslint-disable */
id: string,
name: string,
bundles: Array<string>,
importMap?: {[string]: string},
/* eslint-enable */
];

export const ID = 0;
export const NAME = 1;
export const BUNDLES = 2;
export const IMPORT_MAP = 3;
1 change: 1 addition & 0 deletions scripts/flow/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ declare const __turbopack_require__: ((id: string) => any) & {
declare var parcelRequire: {
(id: string): any,
load: (url: string) => Promise<mixed>,
extendImportMap: (importMap: {[string]: string}) => void,
meta: {
publicUrl: string,
},
Expand Down

0 comments on commit 37906d4

Please sign in to comment.