diff --git a/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js
index f5c95eaa37c69..f6c01a2dd4333 100644
--- a/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js
+++ b/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js
@@ -23,6 +23,7 @@ import {
enableFilterEmptyStringAttributesDOM,
enableCustomElementPropertySupport,
enableFloat,
+ enableFizzExternalRuntime,
} from 'shared/ReactFeatureFlags';
import type {
@@ -156,6 +157,7 @@ export function createResponseState(
bootstrapScriptContent: string | void,
bootstrapScripts: $ReadOnlyArray | void,
bootstrapModules: $ReadOnlyArray | void,
+ externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
): ResponseState {
const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;
const inlineScriptWithNonce =
@@ -172,6 +174,29 @@ export function createResponseState(
endInlineScript,
);
}
+ if (enableFizzExternalRuntime) {
+ if (externalRuntimeConfig !== undefined) {
+ const src =
+ typeof externalRuntimeConfig === 'string'
+ ? externalRuntimeConfig
+ : externalRuntimeConfig.src;
+ const integrity =
+ typeof externalRuntimeConfig === 'string'
+ ? undefined
+ : externalRuntimeConfig.integrity;
+ bootstrapChunks.push(
+ startScriptSrc,
+ stringToChunk(escapeTextForBrowser(src)),
+ );
+ if (integrity) {
+ bootstrapChunks.push(
+ scriptIntegirty,
+ stringToChunk(escapeTextForBrowser(integrity)),
+ );
+ }
+ bootstrapChunks.push(endAsyncScript);
+ }
+ }
if (bootstrapScripts !== undefined) {
for (let i = 0; i < bootstrapScripts.length; i++) {
const scriptConfig = bootstrapScripts[i];
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index 82d103cfecf89..07a6684b1b809 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -3546,6 +3546,36 @@ describe('ReactDOMFizzServer', () => {
});
});
+ // @gate enableFizzExternalRuntime
+ it('supports option to load runtime as an external script', async () => {
+ await actIntoEmptyDocument(() => {
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+
+
+
+ hello world
+
+ ,
+ {
+ unstable_externalRuntimeSrc: 'src-of-external-runtime',
+ },
+ );
+ pipe(writable);
+ });
+
+ expect(getVisibleChildren(document)).toEqual(
+
+
+
+ hello world
+
+ ,
+ );
+ expect(
+ Array.from(document.getElementsByTagName('script')).map(n => n.outerHTML),
+ ).toEqual(['']);
+ });
+
it('#24384: Suspending should halt hydration warnings and not emit any if hydration completes successfully after unsuspending', async () => {
const makeApp = () => {
let resolve, resolved;
diff --git a/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js b/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
index 4285b75a804fa..ffe7e07d10732 100644
--- a/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
+++ b/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
@@ -34,7 +34,7 @@ type Options = {
progressiveChunkSize?: number,
signal?: AbortSignal,
onError?: (error: mixed) => ?string,
- unstable_externalRuntimeSrc?: string,
+ unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
// TODO: Move to sub-classing ReadableStream.
diff --git a/packages/react-dom/src/server/ReactDOMFizzServerNode.js b/packages/react-dom/src/server/ReactDOMFizzServerNode.js
index 5280dd50a13a5..ccc930cf8890d 100644
--- a/packages/react-dom/src/server/ReactDOMFizzServerNode.js
+++ b/packages/react-dom/src/server/ReactDOMFizzServerNode.js
@@ -47,6 +47,7 @@ type Options = {
onShellError?: (error: mixed) => void,
onAllReady?: () => void,
onError?: (error: mixed) => ?string,
+ unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
type PipeableStream = {
@@ -65,6 +66,7 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
+ options ? options.unstable_externalRuntimeSrc : undefined,
),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
diff --git a/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js b/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
index 743e807a0594f..7607579db56cf 100644
--- a/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
+++ b/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
@@ -33,6 +33,7 @@ type Options = {
progressiveChunkSize?: number,
signal?: AbortSignal,
onError?: (error: mixed) => ?string,
+ unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
type StaticResult = {
@@ -71,6 +72,7 @@ function prerender(
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
+ options ? options.unstable_externalRuntimeSrc : undefined,
),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
diff --git a/packages/react-dom/src/server/ReactDOMFizzStaticNode.js b/packages/react-dom/src/server/ReactDOMFizzStaticNode.js
index 74631c3e19f8f..0a8ff59a3f4a5 100644
--- a/packages/react-dom/src/server/ReactDOMFizzStaticNode.js
+++ b/packages/react-dom/src/server/ReactDOMFizzStaticNode.js
@@ -35,6 +35,7 @@ type Options = {
progressiveChunkSize?: number,
signal?: AbortSignal,
onError?: (error: mixed) => ?string,
+ unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
type StaticResult = {
@@ -86,6 +87,7 @@ function prerenderToNodeStreams(
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
+ options ? options.unstable_externalRuntimeSrc : undefined,
),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,