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,