Add option to load Fizz runtime from external file (#25499)

* 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.
This commit is contained in:
Andrew Clark
2022-12-03 10:40:13 -05:00
committed by Rick Hanlon
parent f2d94fdb33
commit 1d2c855e34
14 changed files with 73 additions and 0 deletions
@@ -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<string | BootstrapScriptDescriptor> | void,
bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | 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];
@@ -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(
<html>
<head />
<body>
<div>hello world</div>
</body>
</html>,
{
unstable_externalRuntimeSrc: 'src-of-external-runtime',
},
);
pipe(writable);
});
expect(getVisibleChildren(document)).toEqual(
<html>
<head />
<body>
<div>hello world</div>
</body>
</html>,
);
expect(
Array.from(document.getElementsByTagName('script')).map(n => n.outerHTML),
).toEqual(['<script src="src-of-external-runtime" async=""></script>']);
});
it('#24384: Suspending should halt hydration warnings and not emit any if hydration completes successfully after unsuspending', async () => {
const makeApp = () => {
let resolve, resolved;
@@ -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,
@@ -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,
@@ -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,
@@ -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,
+3
View File
@@ -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
//
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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);