mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
37906d4dfb
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.
130 lines
3.3 KiB
JavaScript
130 lines
3.3 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 {ReactClientValue} from 'react-server/src/ReactFlightServer';
|
|
|
|
export type ServerReference<T: Function> = T & {
|
|
$$typeof: symbol,
|
|
$$id: string,
|
|
$$bound: null | Array<ReactClientValue>,
|
|
$$location?: Error,
|
|
};
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
export type ClientReference<T> = {
|
|
$$typeof: symbol,
|
|
$$id: string,
|
|
$$name: string,
|
|
$$bundles: Array<string>,
|
|
$$importMap?: ?{[string]: string},
|
|
};
|
|
|
|
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
|
|
const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
|
|
|
|
export function isClientReference(reference: Object): boolean {
|
|
return reference.$$typeof === CLIENT_REFERENCE_TAG;
|
|
}
|
|
|
|
export function isServerReference(reference: Object): boolean {
|
|
return reference.$$typeof === SERVER_REFERENCE_TAG;
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
// $FlowFixMe[method-unbinding]
|
|
const FunctionBind = Function.prototype.bind;
|
|
// $FlowFixMe[method-unbinding]
|
|
const ArraySlice = Array.prototype.slice;
|
|
function bind(this: ServerReference<any>): any {
|
|
// $FlowFixMe[prop-missing]
|
|
const newFn = FunctionBind.apply(this, arguments);
|
|
if (this.$$typeof === SERVER_REFERENCE_TAG) {
|
|
if (__DEV__) {
|
|
const thisBind = arguments[0];
|
|
if (thisBind != null) {
|
|
console.error(
|
|
'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
|
|
);
|
|
}
|
|
}
|
|
const args = ArraySlice.call(arguments, 1);
|
|
const $$typeof = {value: SERVER_REFERENCE_TAG};
|
|
const $$id = {value: this.$$id};
|
|
const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args};
|
|
return Object.defineProperties(
|
|
(newFn: any),
|
|
__DEV__
|
|
? {
|
|
$$typeof,
|
|
$$id,
|
|
$$bound,
|
|
$$location: {
|
|
value: this.$$location,
|
|
configurable: true,
|
|
},
|
|
bind: {value: bind, configurable: true},
|
|
}
|
|
: {
|
|
$$typeof,
|
|
$$id,
|
|
$$bound,
|
|
bind: {value: bind, configurable: true},
|
|
},
|
|
);
|
|
}
|
|
return newFn;
|
|
}
|
|
|
|
export function registerServerReference<T>(
|
|
reference: ServerReference<T>,
|
|
id: string,
|
|
exportName: string,
|
|
): ServerReference<T> {
|
|
const $$typeof = {value: SERVER_REFERENCE_TAG};
|
|
const $$id = {
|
|
value: id + '#' + exportName,
|
|
configurable: true,
|
|
};
|
|
const $$bound = {value: null, configurable: true};
|
|
return Object.defineProperties(
|
|
(reference: any),
|
|
__DEV__
|
|
? {
|
|
$$typeof,
|
|
$$id,
|
|
$$bound,
|
|
$$location: {
|
|
value: Error('react-stack-top-frame'),
|
|
configurable: true,
|
|
},
|
|
bind: {value: bind, configurable: true},
|
|
}
|
|
: {
|
|
$$typeof,
|
|
$$id,
|
|
$$bound,
|
|
bind: {value: bind, configurable: true},
|
|
},
|
|
);
|
|
}
|