Files
react/packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js
T
Devon Govett 37906d4dfb [Flight Parcel] Pass import maps through client references (#32132)
Corresponding Parcel PR:
https://github.com/parcel-bundler/parcel/pull/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
(https://github.com/whatwg/html/pull/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.
2025-01-27 15:39:56 -05:00

83 lines
2.1 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Thenable} from 'shared/ReactTypes';
import type {ImportMetadata} from '../shared/ReactFlightImportMetadata';
import {
ID,
NAME,
BUNDLES,
IMPORT_MAP,
} from '../shared/ReactFlightImportMetadata';
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
export type ServerManifest = {
[string]: Array<string>,
};
export type SSRModuleMap = null;
export type ModuleLoading = null;
export type ServerConsumerModuleMap = null;
export type ServerReferenceId = string;
export opaque type ClientReferenceMetadata = ImportMetadata;
// eslint-disable-next-line no-unused-vars
export opaque type ClientReference<T> = ImportMetadata;
export function prepareDestinationForModule(
moduleLoading: ModuleLoading,
nonce: ?string,
metadata: ClientReferenceMetadata,
) {
prepareDestinationWithChunks(moduleLoading, metadata[BUNDLES], nonce);
}
export function resolveClientReference<T>(
bundlerConfig: null,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
// Reference is already resolved during the build.
return metadata;
}
export function resolveServerReference<T>(
bundlerConfig: ServerManifest,
ref: ServerReferenceId,
): ClientReference<T> {
const idx = ref.lastIndexOf('#');
const id = ref.slice(0, idx);
const name = ref.slice(idx + 1);
const bundles = bundlerConfig[id];
if (!bundles) {
throw new Error('Invalid server action: ' + ref);
}
return [id, name, bundles];
}
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)));
}
export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = parcelRequire(metadata[ID]);
return moduleExports[metadata[NAME]];
}