Preinits should support a nonce option (#26744)
Currently there is no way to provide a nonce when using `ReactDOM.preinit(..., { as: 'script' })` This PR adds `nonce?: string` as an option While implementing this PR I added a test to also show you can pass `integrity`. This test isn't directly related to the nonce change.
Josh Story committed
Apr 28, 2023 at 15:57 UTC
b12bea62d9cfd9a925f28cb2c93daeda3865a64e
4 files changed
+105
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+2
@@ -2203,6 +2203,7 @@ type PreinitOptions = {
2203
precedence?: string,
2204
crossOrigin?: string,
2205
integrity?: string,
2206
+ nonce?: string,
2207
};
2208
function preinit(href: string, options: PreinitOptions) {
2209
if (!enableFloat) {
@@ -2355,6 +2356,7 @@ function scriptPropsFromPreinitOptions(
2356
async: true,
2357
crossOrigin: options.crossOrigin,
2358
integrity: options.integrity,
2359
+ nonce: options.nonce,
2360
};
2361
}
2362
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+2
@@ -5110,6 +5110,7 @@ type PreinitOptions = {
5110
precedence?: string,
5111
crossOrigin?: string,
5112
integrity?: string,
5113
+ nonce?: string,
5114
};
5115
function preinit(href: string, options: PreinitOptions): void {
5116
if (!enableFloat) {
@@ -5449,6 +5450,7 @@ function scriptPropsFromPreinitOptions(
5450
async: true,
5451
crossOrigin: options.crossOrigin,
5452
integrity: options.integrity,
5453
+ nonce: options.nonce,
5454
};
5455
}
5456
packages/react-dom/src/ReactDOMDispatcher.js
+1
@@ -20,6 +20,7 @@ export type PreinitOptions = {
20
precedence?: string,
21
crossOrigin?: string,
22
integrity?: string,
23
+ nonce?: string,
24
};
25
26
export type HostDispatcher = {
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+100
@@ -4309,6 +4309,106 @@ body {
4309
'ReactDOM.preinit(): For `href` "foo", the options provided conflict with another call to `ReactDOM.preinit("foo", { as: "script", ... })`. React will always use the options it first encounters when preinitializing a hoistable script for a given `href` and any later options will be ignored if different. Try updating all calls to `ReactDOM.preinit()` for a given `href` to use the same options, or only call `ReactDOM.preinit()` once per `href`.\n "integrity" option value: "some hash", missing from original options\n "crossOrigin" option value: "anonymous", original option value: "use-credentials"',
4310
]);
4311
});
4312
+
4313
+ it('accepts a `nonce` option for `as: "script"`', async () => {
4314
+ function Component({src}) {
4315
+ ReactDOM.preinit(src, {as: 'script', nonce: 'R4nD0m'});
4316
+ return 'hello';
4317
+ }
4318
+
4319
+ await act(() => {
4320
+ renderToPipeableStream(
4321
+ <html>
4322
+ <body>
4323
+ <Component src="foo" />
4324
+ </body>
4325
+ </html>,
4326
+ {
4327
+ nonce: 'R4nD0m',
4328
+ },
4329
+ ).pipe(writable);
4330
+ });
4331
+
4332
+ expect(getMeaningfulChildren(document)).toEqual(
4333
+ <html>
4334
+ <head>
4335
+ <script async="" src="foo" nonce="R4nD0m" />
4336
+ </head>
4337
+ <body>hello</body>
4338
+ </html>,
4339
+ );
4340
+
4341
+ await clientAct(() => {
4342
+ ReactDOMClient.hydrateRoot(
4343
+ document,
4344
+ <html>
4345
+ <body>
4346
+ <Component src="bar" />
4347
+ </body>
4348
+ </html>,
4349
+ );
4350
+ });
4351
+
4352
+ expect(getMeaningfulChildren(document)).toEqual(
4353
+ <html>
4354
+ <head>
4355
+ <script async="" src="foo" nonce="R4nD0m" />
4356
+ <script async="" src="bar" nonce="R4nD0m" />
4357
+ </head>
4358
+ <body>hello</body>
4359
+ </html>,
4360
+ );
4361
+ });
4362
+
4363
+ it('accepts an `integrity` option for `as: "script"`', async () => {
4364
+ function Component({src, hash}) {
4365
+ ReactDOM.preinit(src, {as: 'script', integrity: hash});
4366
+ return 'hello';
4367
+ }
4368
+
4369
+ await act(() => {
4370
+ renderToPipeableStream(
4371
+ <html>
4372
+ <body>
4373
+ <Component src="foo" hash="foo hash" />
4374
+ </body>
4375
+ </html>,
4376
+ {
4377
+ nonce: 'R4nD0m',
4378
+ },
4379
+ ).pipe(writable);
4380
+ });
4381
+
4382
+ expect(getMeaningfulChildren(document)).toEqual(
4383
+ <html>
4384
+ <head>
4385
+ <script async="" src="foo" integrity="foo hash" />
4386
+ </head>
4387
+ <body>hello</body>
4388
+ </html>,
4389
+ );
4390
+
4391
+ await clientAct(() => {
4392
+ ReactDOMClient.hydrateRoot(
4393
+ document,
4394
+ <html>
4395
+ <body>
4396
+ <Component src="bar" hash="bar hash" />
4397
+ </body>
4398
+ </html>,
4399
+ );
4400
+ });
4401
+
4402
+ expect(getMeaningfulChildren(document)).toEqual(
4403
+ <html>
4404
+ <head>
4405
+ <script async="" src="foo" integrity="foo hash" />
4406
+ <script async="" src="bar" integrity="bar hash" />
4407
+ </head>
4408
+ <body>hello</body>
4409
+ </html>,
4410
+ );
4411
+ });
4412
});
4413
4414
describe('Stylesheet Resources', () => {