Skip to content

Commit

Permalink
bootstrap scripts now preload
Browse files Browse the repository at this point in the history
  • Loading branch information
gnoff committed May 1, 2023
1 parent 8ea96ef commit 2899034
Show file tree
Hide file tree
Showing 19 changed files with 109 additions and 14 deletions.
36 changes: 36 additions & 0 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export type ExternalRuntimeScript = {
// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag
// is set, the server will send instructions via data attributes (instead of inline scripts)
export function createResponseState(
resources: Resources,
identifierPrefix: string | void,
nonce: string | void,
bootstrapScriptContent: string | void,
Expand Down Expand Up @@ -266,6 +267,8 @@ export function createResponseState(
const integrity =
typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity;

preloadBootstrapScript(resources, src);

bootstrapChunks.push(
startScriptSrc,
stringToChunk(escapeTextForBrowser(src)),
Expand Down Expand Up @@ -5378,6 +5381,39 @@ function preinit(href: string, options: PreinitOptions): void {
}
}

// This function is only safe to call at Request start time since it assumes
// that each script has not already been preloaded. If we find a need to preload
// scripts at any other point in time we will need to check whether the preload
// already exists and not assume it
function preloadBootstrapScript(resources: Resources, src: string): void {
const key = getResourceKey('script', src);
if (__DEV__) {
if (resources.preloadsMap.has(key)) {
// This is coded as a React error because it should be impossible for a userspace preload to preempt this call
// If a userspace preload can preempt it then this assumption is broken and we need to reconsider this strategy
// rather than instruct the user to not preload their bootstrap scripts themselves
console.error(
'Internal React Error: React expected bootstrap script with src "%s" to not have been preloaded already. please file an issue',
src,
);
}
}
const props: PreloadProps = {
rel: 'preload',
href: src,
as: 'script',
};
const resource: PreloadResource = {
type: 'preload',
chunks: [],
state: NoState,
props,
};
resources.preloadsMap.set(key, resource);
resources.explicitScriptPreloads.add(resource);
pushLinkImpl(resource.chunks, props);
}

function internalPreinitScript(
resources: Resources,
src: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import type {
Resources,
BootstrapScriptDescriptor,
ExternalRuntimeScript,
FormatContext,
Expand Down Expand Up @@ -63,11 +64,13 @@ export type ResponseState = {
};

export function createResponseState(
resources: Resources,
generateStaticMarkup: boolean,
identifierPrefix: string | void,
externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
): ResponseState {
const responseState = createResponseStateImpl(
resources,
identifierPrefix,
undefined,
undefined,
Expand Down
16 changes: 13 additions & 3 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,10 @@ describe('ReactDOMFizzServer', () => {
pipe(writable);
});

expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
expect(getVisibleChildren(container)).toEqual([
<link rel="preload" href="init.js" as="script" />,
<div>Loading...</div>,
]);

// check that there are 4 scripts with a matching nonce:
// The runtime script, an inline bootstrap script, and two src scripts
Expand All @@ -614,7 +617,10 @@ describe('ReactDOMFizzServer', () => {
await act(() => {
resolve({default: Text});
});
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
expect(getVisibleChildren(container)).toEqual([
<link rel="preload" href="init.js" as="script" />,
<div>Hello</div>,
]);
} finally {
CSPnonce = null;
}
Expand Down Expand Up @@ -3662,7 +3668,11 @@ describe('ReactDOMFizzServer', () => {

expect(getVisibleChildren(document)).toEqual(
<html>
<head />
<head>
<link rel="preload" href="foo" as="script" />
<link rel="preload" href="bar" as="script" />
<link rel="preload" href="baz" as="script" />
</head>
<body>
<div>hello world</div>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('ReactDOMFizzServerBrowser', () => {
);
const result = await readResult(stream);
expect(result).toMatchInlineSnapshot(
`"<div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
`"<link rel="preload" href="init.js" as="script"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
);
});

Expand Down Expand Up @@ -500,7 +500,7 @@ describe('ReactDOMFizzServerBrowser', () => {
);
const result = await readResult(stream);
expect(result).toMatchInlineSnapshot(
`"<div>hello world</div><script nonce="${nonce}">INIT();</script><script src="init.js" nonce="${nonce}" async=""></script><script type="module" src="init.mjs" nonce="${nonce}" async=""></script>"`,
`"<link rel="preload" href="init.js" as="script"/><div>hello world</div><script nonce="${nonce}">INIT();</script><script src="init.js" nonce="${nonce}" async=""></script><script type="module" src="init.mjs" nonce="${nonce}" async=""></script>"`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('ReactDOMFizzServerNode', () => {
pipe(writable);
jest.runAllTimers();
expect(output.result).toMatchInlineSnapshot(
`"<div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
`"<link rel="preload" href="init.js" as="script"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
});
const prelude = await readContent(result.prelude);
expect(prelude).toMatchInlineSnapshot(
`"<div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
`"<link rel="preload" href="init.js" as="script"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ describe('ReactDOMFizzStaticNode', () => {
);
const prelude = await readContent(result.prelude);
expect(prelude).toMatchInlineSnapshot(
`"<div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
`"<link rel="preload" href="init.js" as="script"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
);
});

Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
Expand Down Expand Up @@ -79,9 +80,12 @@ function renderToReadableStream(
allReady.catch(() => {});
reject(error);
}
const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom/src/server/ReactDOMFizzServerBun.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
Expand Down Expand Up @@ -80,9 +81,12 @@ function renderToReadableStream(
allReady.catch(() => {});
reject(error);
}
const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom/src/server/ReactDOMFizzServerEdge.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
Expand Down Expand Up @@ -79,9 +80,12 @@ function renderToReadableStream(
allReady.catch(() => {});
reject(error);
}
const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom/src/server/ReactDOMFizzServerNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
Expand Down Expand Up @@ -59,9 +60,12 @@ type PipeableStream = {
};

function createRequestImpl(children: ReactNodeList, options: void | Options) {
const resources = createResources();
return createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
Expand Down Expand Up @@ -64,9 +65,12 @@ function prerender(
};
resolve(result);
}
const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom/src/server/ReactDOMFizzStaticEdge.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
Expand Down Expand Up @@ -64,9 +65,12 @@ function prerender(
};
resolve(result);
}
const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
5 changes: 4 additions & 1 deletion packages/react-dom/src/server/ReactDOMFizzStaticNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
Expand Down Expand Up @@ -78,10 +79,12 @@ function prerenderToNodeStreams(
};
resolve(result);
}

const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dom/src/server/ReactDOMLegacyServerImpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
Expand Down Expand Up @@ -61,9 +62,12 @@ function renderToStringImpl(
function onShellReady() {
readyToStream = true;
}
const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
generateStaticMarkup,
options ? options.identifierPrefix : undefined,
unstable_externalRuntimeSrc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
Expand Down Expand Up @@ -70,9 +71,15 @@ function renderToNodeStreamImpl(
startFlowing(request, destination);
}
const destination = new ReactMarkupReadableStream();
const resources = createResources();
const request = createRequest(
children,
createResponseState(false, options ? options.identifierPrefix : undefined),
resources,
createResponseState(
resources,
false,
options ? options.identifierPrefix : undefined,
),
createRootFormatContext(),
Infinity,
onError,
Expand Down
4 changes: 4 additions & 0 deletions packages/react-server-dom-relay/src/ReactDOMServerFB.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from 'react-server/src/ReactFizzServer';

import {
createResources,
createResponseState,
createRootFormatContext,
} from 'react-server/src/ReactFizzConfig';
Expand All @@ -49,9 +50,12 @@ function renderToStream(children: ReactNodeList, options: Options): Stream {
fatal: false,
error: null,
};
const resources = createResources();
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('ReactDOMServerFB', () => {
});
const result = readResult(stream);
expect(result).toMatchInlineSnapshot(
`"<div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
`"<link rel="preload" href="init.js" as="script"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
);
});

Expand Down
Loading

0 comments on commit 2899034

Please sign in to comment.