From 500bea532d1b349fd5681b6477c14cedf4ed54ac Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Mon, 17 Oct 2022 17:57:59 -0400 Subject: [PATCH] Add option to load Fizz runtime from external file (#25499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add feature flag for external Fizz runtime Only enabled for www for now * Add option to load Fizz runtime from external file When unstable_externalRuntimeSrc is provided, React will inject a script tag that points to the provided URL. Then, instead of emitting inline scripts, the Fizz stream will emit HTML nodes with data attributes that encode the instructions. The external runtime will detect these with a mutation observer and translate them into runtime commands. This part isn't implemented in this PR, though — all this does is set up the option to use an external runtime, and inject the script tag. The external runtime is injected at the same time as bootstrap scripts. --- .../src/server/ReactDOMServerFormatConfig.js | 25 ++++++++++++++++ .../src/__tests__/ReactDOMFizzServer-test.js | 30 +++++++++++++++++++ .../src/server/ReactDOMFizzServerBrowser.js | 2 ++ .../src/server/ReactDOMFizzServerNode.js | 2 ++ .../src/server/ReactDOMFizzStaticBrowser.js | 2 ++ .../src/server/ReactDOMFizzStaticNode.js | 2 ++ packages/shared/ReactFeatureFlags.js | 3 ++ .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.testing.js | 1 + .../forks/ReactFeatureFlags.testing.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 14 files changed, 73 insertions(+) diff --git a/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js index 1a2f466263b1d..05f54acb56df3 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 { @@ -157,6 +158,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 = @@ -173,6 +175,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 2394f656c53a7..ffe7e07d10732 100644 --- a/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js +++ b/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js @@ -34,6 +34,7 @@ type Options = { progressiveChunkSize?: number, signal?: AbortSignal, onError?: (error: mixed) => ?string, + unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor, }; // TODO: Move to sub-classing ReadableStream. @@ -86,6 +87,7 @@ function renderToReadableStream( 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/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, diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index f43beacb79d7e..73600f26f066a 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -125,6 +125,9 @@ export const enableUseMemoCacheHook = __EXPERIMENTAL__; export const enableUseEventHook = __EXPERIMENTAL__; +// Test in www before enabling in open source. +export const enableFizzExternalRuntime = false; + // ----------------------------------------------------------------------------- // Chopping Block // diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 751377fbb1d33..b241a874da044 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -86,6 +86,7 @@ export const enableFloat = false; export const enableHostSingletons = false; export const useModernStrictMode = false; +export const enableFizzExternalRuntime = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 7dc0ac1e2febc..31bb0838a847d 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -76,6 +76,7 @@ export const enableFloat = false; export const enableHostSingletons = false; export const useModernStrictMode = false; +export const enableFizzExternalRuntime = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 2d408c9c10f36..5c910a102907f 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -76,6 +76,7 @@ export const enableFloat = false; export const enableHostSingletons = false; export const useModernStrictMode = false; +export const enableFizzExternalRuntime = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 9ff5908cc4e87..6c0ee8735c287 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -78,6 +78,7 @@ export const enableFloat = false; export const enableHostSingletons = false; export const useModernStrictMode = false; +export const enableFizzExternalRuntime = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index f2346a66aa30f..18add772cb481 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -76,6 +76,7 @@ export const enableFloat = false; export const enableHostSingletons = false; export const useModernStrictMode = false; +export const enableFizzExternalRuntime = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index 7114d9d21289f..152189a407e17 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -77,6 +77,7 @@ export const enableFloat = false; export const enableHostSingletons = false; export const useModernStrictMode = false; +export const enableFizzExternalRuntime = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 755d44053c465..fb44dca295e00 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -113,6 +113,7 @@ export const enableUseMutableSource = true; export const enableCustomElementPropertySupport = __EXPERIMENTAL__; export const useModernStrictMode = false; +export const enableFizzExternalRuntime = true; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType);