From 1d2c855e34272e754ef97252ed9b936bed92d017 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 1a2f466263..05f54acb56 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 82d103cfec..07a6684b1b 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 2394f656c5..ffe7e07d10 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 5280dd50a1..ccc930cf88 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 743e807a05..7607579db5 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 74631c3e19..0a8ff59a3f 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 f43beacb79..73600f26f0 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 751377fbb1..b241a874da 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 7dc0ac1e2f..31bb0838a8 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 2d408c9c10..5c910a1029 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 9ff5908cc4..6c0ee8735c 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 f2346a66aa..18add772cb 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 7114d9d212..152189a407 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 755d44053c..fb44dca295 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);