Skip to content

Commit cdb8a1d

Browse files
authored
[Fizz] Add option to inject bootstrapping script tags after the shell is injected (#22594)
* Add option to inject bootstrap scripts These are emitted right after the shell as flushed. * Update ssr fixtures to use bootstrapScripts instead of manual script tag * Add option to FB renderer too
1 parent 3677c01 commit cdb8a1d

File tree

20 files changed

+5445
-21
lines changed

20 files changed

+5445
-21
lines changed

fixtures/ssr/server/render.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default function render(url, res) {
2121
});
2222
let didError = false;
2323
const {pipe, abort} = renderToPipeableStream(<App assets={assets} />, {
24+
bootstrapScripts: [assets['main.js']],
2425
onCompleteShell() {
2526
// If something errored before we started streaming, we set the error code appropriately.
2627
res.statusCode = didError ? 500 : 200;

fixtures/ssr/src/components/Chrome.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ export default class Chrome extends Component {
4646
__html: `assetManifest = ${JSON.stringify(assets)};`,
4747
}}
4848
/>
49-
<script src={assets['main.js']} />
5049
</body>
5150
</html>
5251
);

fixtures/ssr/yarn.lock

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4265,7 +4265,7 @@ longest@^1.0.1:
42654265
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
42664266
integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=
42674267

4268-
loose-envify@^1.0.0, loose-envify@^1.1.0:
4268+
loose-envify@^1.0.0:
42694269
version "1.4.0"
42704270
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
42714271
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -5945,14 +5945,6 @@ sax@^1.2.1, sax@~1.2.1:
59455945
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
59465946
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
59475947

5948-
scheduler@^0.20.1:
5949-
version "0.20.2"
5950-
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
5951-
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
5952-
dependencies:
5953-
loose-envify "^1.1.0"
5954-
object-assign "^4.1.1"
5955-
59565948
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
59575949
version "5.7.1"
59585950
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"

fixtures/ssr2/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
"concurrently": "^5.3.0",
1616
"express": "^4.17.1",
1717
"nodemon": "^2.0.6",
18-
"react": "18.0.0-alpha-7ec4c5597",
19-
"react-dom": "18.0.0-alpha-7ec4c5597",
18+
"react": "link:../../build/node_modules/react",
19+
"react-dom": "link:../../build/node_modules/react-dom",
2020
"react-error-boundary": "^3.1.3",
2121
"resolve": "1.12.0",
2222
"rimraf": "^3.0.2",

fixtures/ssr2/server/render.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ module.exports = function render(url, res) {
4242
<App assets={assets} />
4343
</DataProvider>,
4444
{
45+
bootstrapScripts: [assets['main.js']],
4546
onCompleteShell() {
4647
// If something errored before we started streaming, we set the error code appropriately.
4748
res.statusCode = didError ? 500 : 200;

fixtures/ssr2/src/Html.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export default function Html({assets, children, title}) {
2828
__html: `assetManifest = ${JSON.stringify(assets)};`,
2929
}}
3030
/>
31-
<script async src={assets['main.js']} />
3231
</body>
3332
</html>
3433
);

fixtures/ssr2/yarn.lock

Lines changed: 5277 additions & 0 deletions
Large diffs are not rendered by default.

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ let useSyncExternalStore;
2121
let useSyncExternalStoreExtra;
2222
let PropTypes;
2323
let textCache;
24+
let window;
2425
let document;
2526
let writable;
2627
let CSPnonce = null;
@@ -56,6 +57,7 @@ describe('ReactDOMFizzServer', () => {
5657
runScripts: 'dangerously',
5758
},
5859
);
60+
window = jsdom.window;
5961
document = jsdom.window.document;
6062
container = document.getElementById('container');
6163

@@ -338,11 +340,18 @@ describe('ReactDOMFizzServer', () => {
338340
);
339341
}
340342

343+
let bootstrapped = false;
344+
window.__INIT__ = function() {
345+
bootstrapped = true;
346+
// Attempt to hydrate the content.
347+
ReactDOM.hydrateRoot(container, <App isClient={true} />);
348+
};
349+
341350
await act(async () => {
342351
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
343352
<App isClient={false} />,
344-
345353
{
354+
bootstrapScriptContent: '__INIT__();',
346355
onError(x) {
347356
loggedErrors.push(x);
348357
},
@@ -351,10 +360,8 @@ describe('ReactDOMFizzServer', () => {
351360
pipe(writable);
352361
});
353362
expect(loggedErrors).toEqual([]);
363+
expect(bootstrapped).toBe(true);
354364

355-
// Attempt to hydrate the content.
356-
const root = ReactDOM.createRoot(container, {hydrate: true});
357-
root.render(<App isClient={true} />);
358365
Scheduler.unstable_flushAll();
359366

360367
// We're still loading because we're waiting for the server to stream more content.
@@ -507,17 +514,27 @@ describe('ReactDOMFizzServer', () => {
507514
);
508515
}
509516

517+
let bootstrapped = false;
518+
window.__INIT__ = function() {
519+
bootstrapped = true;
520+
// Attempt to hydrate the content.
521+
ReactDOM.hydrateRoot(container, <App />);
522+
};
523+
510524
await act(async () => {
511-
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
525+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
526+
bootstrapScriptContent: '__INIT__();',
527+
});
512528
pipe(writable);
513529
});
514530

515531
// We're still showing a fallback.
516532
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
517533

534+
// We already bootstrapped.
535+
expect(bootstrapped).toBe(true);
536+
518537
// Attempt to hydrate the content.
519-
const root = ReactDOM.createRoot(container, {hydrate: true});
520-
root.render(<App />);
521538
Scheduler.unstable_flushAll();
522539

523540
// We're still loading because we're waiting for the server to stream more content.

packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ describe('ReactDOMFizzServer', () => {
7171
);
7272
});
7373

74+
// @gate experimental
75+
it('should emit bootstrap script src at the end', async () => {
76+
const stream = ReactDOMFizzServer.renderToReadableStream(
77+
<div>hello world</div>,
78+
{
79+
bootstrapScriptContent: 'INIT();',
80+
bootstrapScripts: ['init.js'],
81+
bootstrapModules: ['init.mjs'],
82+
},
83+
);
84+
const result = await readResult(stream);
85+
expect(result).toMatchInlineSnapshot(
86+
`"<div>hello world</div><script>INIT();</script><script src=\\"init.js\\" async=\\"\\"></script><script type=\\"module\\" src=\\"init.mjs\\" async=\\"\\"></script>"`,
87+
);
88+
});
89+
7490
// @gate experimental
7591
it('emits all HTML as one unit if we wait until the end to start', async () => {
7692
let hasLoaded = false;

packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ describe('ReactDOMFizzServer', () => {
8282
);
8383
});
8484

85+
// @gate experimental
86+
it('should emit bootstrap script src at the end', () => {
87+
const {writable, output} = getTestWritable();
88+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
89+
<div>hello world</div>,
90+
{
91+
bootstrapScriptContent: 'INIT();',
92+
bootstrapScripts: ['init.js'],
93+
bootstrapModules: ['init.mjs'],
94+
},
95+
);
96+
pipe(writable);
97+
jest.runAllTimers();
98+
expect(output.result).toMatchInlineSnapshot(
99+
`"<div>hello world</div><script>INIT();</script><script src=\\"init.js\\" async=\\"\\"></script><script type=\\"module\\" src=\\"init.mjs\\" async=\\"\\"></script>"`,
100+
);
101+
});
102+
85103
// @gate experimental
86104
it('should start writing after pipe', () => {
87105
const {writable, output} = getTestWritable();

0 commit comments

Comments
 (0)