@samitouri / QOS-React-1 / commits / ed4bd540ca

[Flight] Warn once if `eval` is disabled in dev environment (#35661)

Sebastian "Sebbie" Silbermann committed Feb 2, 2026 at 12:56 UTC ed4bd540ca67f1c4db65f009ad726a5ae1a0af01
26 files changed +289 -11
packages/react-client/src/ReactClientDebugConfigBrowser.js new
+36
@@ -0,0 +1,36 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +let hasConfirmedEval = false;
11 +export function checkEvalAvailabilityOnceDev(): void {
12 + if (__DEV__) {
13 + if (!hasConfirmedEval) {
14 + hasConfirmedEval = true;
15 + try {
16 + // eslint-disable-next-line no-eval
17 + (0, eval)('null');
18 + } catch {
19 + console.error(
20 + 'eval() is not supported in this environment. ' +
21 + 'If this page was served with a `Content-Security-Policy` header, ' +
22 + 'make sure that `unsafe-eval` is included. ' +
23 + 'React requires eval() in development mode for various debugging features ' +
24 + 'like reconstructing callstacks from a different environment.\n' +
25 + 'React will never use eval() in production mode',
26 + );
27 + }
28 + }
29 + } else {
30 + // These errors should never make it into a build so we don't need to encode them in codes.json
31 + // eslint-disable-next-line react-internal/prod-error-codes
32 + throw new Error(
33 + 'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
34 + );
35 + }
36 +}
packages/react-client/src/ReactClientDebugConfigNode.js new
+36
@@ -0,0 +1,36 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +let hasConfirmedEval = false;
11 +export function checkEvalAvailabilityOnceDev(): void {
12 + if (__DEV__) {
13 + if (!hasConfirmedEval) {
14 + hasConfirmedEval = true;
15 + try {
16 + // eslint-disable-next-line no-eval
17 + (0, eval)('null');
18 + } catch {
19 + console.error(
20 + 'eval() is not supported in this environment. ' +
21 + 'This can happen if you started the Node.js process with --disallow-code-generation-from-strings, ' +
22 + 'or if `eval` was patched by other means. ' +
23 + 'React requires eval() in development mode for various debugging features ' +
24 + 'like reconstructing callstacks from a different environment.\n' +
25 + 'React will never use eval() in production mode',
26 + );
27 + }
28 + }
29 + } else {
30 + // These errors should never make it into a build so we don't need to encode them in codes.json
31 + // eslint-disable-next-line react-internal/prod-error-codes
32 + throw new Error(
33 + 'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
34 + );
35 + }
36 +}
packages/react-client/src/ReactClientDebugConfigPlain.js new
+34
@@ -0,0 +1,34 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +let hasConfirmedEval = false;
11 +export function checkEvalAvailabilityOnceDev(): void {
12 + if (__DEV__) {
13 + if (!hasConfirmedEval) {
14 + hasConfirmedEval = true;
15 + try {
16 + // eslint-disable-next-line no-eval
17 + (0, eval)('null');
18 + } catch {
19 + console.error(
20 + 'eval() is not supported in this environment. ' +
21 + 'React requires eval() in development mode for various debugging features ' +
22 + 'like reconstructing callstacks from a different environment.\n' +
23 + 'React will never use eval() in production mode',
24 + );
25 + }
26 + }
27 + } else {
28 + // These errors should never make it into a build so we don't need to encode them in codes.json
29 + // eslint-disable-next-line react-internal/prod-error-codes
30 + throw new Error(
31 + 'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
32 + );
33 + }
34 +}
packages/react-client/src/ReactFlightClient.js
+9
@@ -61,6 +61,7 @@ import {
61 bindToConsole,
62 rendererVersion,
63 rendererPackageName,
64 + checkEvalAvailabilityOnceDev,
65 } from './ReactFlightClientConfig';
66
67 import {
@@ -2768,6 +2769,14 @@ export function createResponse(
2769 debugEndTime: void | number, // DEV-only
2770 debugChannel: void | DebugChannel, // DEV-only
2771 ): WeakResponse {
2772 + if (__DEV__) {
2773 + // We use eval to create fake function stacks which includes Component stacks.
2774 + // A warning would be noise if you used Flight without Components and don't encounter
2775 + // errors. We're warning eagerly so that you configure your environment accordingly
2776 + // before you encounter an error.
2777 + checkEvalAvailabilityOnceDev();
2778 + }
2779 +
2780 return getWeakResponse(
2781 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
2782 new ResponseInstance(
packages/react-client/src/ReactFlightReplyClient.js
+12 -1
@@ -39,7 +39,10 @@ import getPrototypeOf from 'shared/getPrototypeOf';
39
40 const ObjectPrototype = Object.prototype;
41
42 -import {usedWithSSR} from './ReactFlightClientConfig';
42 +import {
43 + usedWithSSR,
44 + checkEvalAvailabilityOnceDev,
45 +} from './ReactFlightClientConfig';
46
47 type ReactJSONValue =
48 | string
@@ -190,6 +193,14 @@ export function processReply(
193 const writtenObjects: WeakMap<Reference, string> = new WeakMap();
194 let modelRoot: null | ReactServerValue = root;
195
196 + if (__DEV__) {
197 + // We use eval to create fake function stacks which includes Component stacks.
198 + // A warning would be noise if you used Flight without Components and don't encounter
199 + // errors. We're warning eagerly so that you configure your environment accordingly
200 + // before you encounter an error.
201 + checkEvalAvailabilityOnceDev();
202 + }
203 +
204 function serializeTypedArray(
205 tag: string,
206 typedArray: $ArrayBufferView,
packages/react-client/src/__tests__/ReactFlight-test.js
+6
@@ -3715,6 +3715,12 @@ describe('ReactFlight', () => {
3715 '\n in b (at **)' +
3716 '\n in a (at **)',
3717 );
3718 + assertConsoleErrorDev([
3719 + 'eval() is not supported in this environment. ' +
3720 + 'React requires eval() in development mode for various debugging features ' +
3721 + 'like reconstructing callstacks from a different environment.\n' +
3722 + 'React will never use eval() in production mode',
3723 + ]);
3724 } else {
3725 expect(receivedError.message).toEqual(
3726 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.',
packages/react-client/src/forks/ReactFlightClientConfig.custom.js
+3
@@ -53,3 +53,6 @@ export const bindToConsole = $$$config.bindToConsole;
53
54 export const rendererVersion = $$$config.rendererVersion;
55 export const rendererPackageName = $$$config.rendererPackageName;
56 +
57 +export const checkEvalAvailabilityOnceDev =
58 + $$$config.checkEvalAvailabilityOnceDev;
packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-esm';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigBrowser';
15 +export * from 'react-client/src/ReactClientDebugConfigBrowser';
16 export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM';
17 export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigTargetESMBrowser';
18 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-parcel';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigBrowser';
15 +export * from 'react-client/src/ReactClientDebugConfigBrowser';
16 export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel';
17 export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigTargetParcelBrowser';
18 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-turbopack';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigBrowser';
15 +export * from 'react-client/src/ReactClientDebugConfigBrowser';
16 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
17 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser';
18 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackBrowser';
packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-webpack';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigBrowser';
15 +export * from 'react-client/src/ReactClientDebugConfigBrowser';
16 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
17 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser';
18 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackBrowser';
packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-bun';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigPlain';
15 +export * from 'react-client/src/ReactClientDebugConfigPlain';
16 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
17
18 export opaque type ModuleLoading = mixed;
packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-parcel';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigPlain';
16 export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel';
17 export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigTargetParcelServer';
18 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-turbopack';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigPlain';
16 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
17 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer';
18 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackServer';
packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-webpack';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigPlain';
16 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
17 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
18 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'not-used';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 export * from 'react-client/src/ReactClientConsoleConfigBrowser';
15 +export * from 'react-client/src/ReactClientDebugConfigBrowser';
16
17 export type Response = any;
18 export opaque type ModuleLoading = mixed;
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-esm';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigNode';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigNode';
16 export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM';
17 export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigTargetESMServer';
18 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-parcel';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigNode';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigNode';
16 export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel';
17 export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigTargetParcelServer';
18 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-turbopack';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigNode';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigNode';
16 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
17 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer';
18 export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackServer';
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-unbundled.js
+1
@@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-unbundled';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigNode';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigNode';
16 export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode';
17 export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer';
18 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js
+1
@@ -11,6 +11,7 @@ export const rendererPackageName = 'react-server-dom-webpack';
11
12 export * from 'react-client/src/ReactFlightClientStreamConfigNode';
13 export * from 'react-client/src/ReactClientConsoleConfigServer';
14 +export * from 'react-client/src/ReactClientDebugConfigNode';
15 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
16 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
17 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
packages/react-client/src/forks/ReactFlightClientConfig.markup.js
+1
@@ -14,6 +14,7 @@ import type {Thenable} from 'shared/ReactTypes';
14
15 export * from 'react-markup/src/ReactMarkupLegacyClientStreamConfig.js';
16 export * from 'react-client/src/ReactClientConsoleConfigPlain';
17 +export * from 'react-client/src/ReactClientDebugConfigPlain';
18
19 export type ModuleLoading = null;
20 export type ServerConsumerModuleMap = null;
packages/react-noop-renderer/src/ReactNoopFlightClient.js
+25
@@ -53,6 +53,7 @@ const {createResponse, createStreamState, processBinaryChunk, getRoot, close} =
53 [console].concat(args),
54 );
55 },
56 + checkEvalAvailabilityOnceDev,
57 });
58
59 type ReadOptions = {|
@@ -87,4 +88,28 @@ function read<T>(source: Source, options: ReadOptions): Thenable<T> {
88 return getRoot(response);
89 }
90
91 +let hasConfirmedEval = false;
92 +function checkEvalAvailabilityOnceDev(): void {
93 + if (__DEV__) {
94 + if (!hasConfirmedEval) {
95 + hasConfirmedEval = true;
96 + try {
97 + // eslint-disable-next-line no-eval
98 + (0, eval)('null');
99 + } catch {
100 + console.error(
101 + 'eval() is not supported in this environment. ' +
102 + 'React requires eval() in development mode for various debugging features ' +
103 + 'like reconstructing callstacks from a different environment.\n' +
104 + 'React will never use eval() in production mode',
105 + );
106 + }
107 + }
108 + } else {
109 + throw new Error(
110 + 'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
111 + );
112 + }
113 +}
114 +
115 export {read};
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReply-test.js
+32
@@ -18,6 +18,7 @@ global.TextEncoder = require('util').TextEncoder;
18 global.TextDecoder = require('util').TextDecoder;
19
20 // let serverExports;
21 +let assertConsoleErrorDev;
22 let turbopackServerMap;
23 let ReactServerDOMServer;
24 let ReactServerDOMClient;
@@ -41,6 +42,9 @@ describe('ReactFlightTurbopackDOMReply', () => {
42 ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
43 jest.resetModules();
44 ReactServerDOMClient = require('react-server-dom-turbopack/client');
45 +
46 + const InternalTestUtils = require('internal-test-utils');
47 + assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
48 });
49
50 it('can encode a reply', async () => {
@@ -52,4 +56,32 @@ describe('ReactFlightTurbopackDOMReply', () => {
56
57 expect(decoded).toEqual({some: 'object'});
58 });
59 +
60 + it('warns with a tailored message if eval is not available in dev', async () => {
61 + // eslint-disable-next-line no-eval
62 + const previousEval = globalThis.eval.bind(globalThis);
63 + // eslint-disable-next-line no-eval
64 + globalThis.eval = () => {
65 + throw new Error('eval is disabled');
66 + };
67 +
68 + try {
69 + const body = await ReactServerDOMClient.encodeReply({some: 'object'});
70 + assertConsoleErrorDev([
71 + 'eval() is not supported in this environment. ' +
72 + 'If this page was served with a `Content-Security-Policy` header, ' +
73 + 'make sure that `unsafe-eval` is included. ' +
74 + 'React requires eval() in development mode for various debugging features ' +
75 + 'like reconstructing callstacks from a different environment.\n' +
76 + 'React will never use eval() in production mode',
77 + ]);
78 +
79 + await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
80 +
81 + assertConsoleErrorDev([]);
82 + } finally {
83 + // eslint-disable-next-line no-eval
84 + globalThis.eval = previousEval;
85 + }
86 + });
87 });
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js
+50 -10
@@ -28,6 +28,7 @@ let ReactServerDOMStaticServer;
28 let ReactServerDOMClient;
29 let Stream;
30 let use;
31 +let assertConsoleErrorDev;
32 let serverAct;
33
34 // We test pass-through without encoding strings but it should work without it too.
@@ -73,6 +74,9 @@ describe('ReactFlightDOMNode', () => {
74 ReactServerDOMClient = require('react-server-dom-webpack/client');
75 Stream = require('stream');
76 use = React.use;
77 +
78 + const InternalTestUtils = require('internal-test-utils');
79 + assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
80 });
81
82 function filterStackFrame(filename, functionName) {
@@ -955,10 +959,10 @@ describe('ReactFlightDOMNode', () => {
959 // The concrete location may change as this test is updated.
960 // Just make sure they still point at React.use(p2)
961 (gate(flags => flags.enableAsyncDebugInfo)
958 - ? '\n at SharedComponent (./ReactFlightDOMNode-test.js:813:7)'
962 + ? '\n at SharedComponent (./ReactFlightDOMNode-test.js:817:7)'
963 : '') +
960 - '\n at ServerComponent (file://./ReactFlightDOMNode-test.js:835:26)' +
961 - '\n at App (file://./ReactFlightDOMNode-test.js:852:25)',
964 + '\n at ServerComponent (file://./ReactFlightDOMNode-test.js:839:26)' +
965 + '\n at App (file://./ReactFlightDOMNode-test.js:856:25)',
966 );
967 } else {
968 expect(ownerStack).toBeNull();
@@ -1545,12 +1549,12 @@ describe('ReactFlightDOMNode', () => {
1549 '\n' +
1550 ' in Dynamic' +
1551 (gate(flags => flags.enableAsyncDebugInfo)
1548 - ? ' (file://ReactFlightDOMNode-test.js:1419:27)\n'
1552 + ? ' (file://ReactFlightDOMNode-test.js:1423:27)\n'
1553 : '\n') +
1554 ' in body\n' +
1555 ' in html\n' +
1552 - ' in App (file://ReactFlightDOMNode-test.js:1432:25)\n' +
1553 - ' in ClientRoot (ReactFlightDOMNode-test.js:1507:16)',
1556 + ' in App (file://ReactFlightDOMNode-test.js:1436:25)\n' +
1557 + ' in ClientRoot (ReactFlightDOMNode-test.js:1511:16)',
1558 );
1559 } else {
1560 expect(
@@ -1559,7 +1563,7 @@ describe('ReactFlightDOMNode', () => {
1563 '\n' +
1564 ' in body\n' +
1565 ' in html\n' +
1562 - ' in ClientRoot (ReactFlightDOMNode-test.js:1507:16)',
1566 + ' in ClientRoot (ReactFlightDOMNode-test.js:1511:16)',
1567 );
1568 }
1569
@@ -1569,8 +1573,8 @@ describe('ReactFlightDOMNode', () => {
1573 normalizeCodeLocInfo(ownerStack, {preserveLocation: true}),
1574 ).toBe(
1575 '\n' +
1572 - ' in Dynamic (file://ReactFlightDOMNode-test.js:1419:27)\n' +
1573 - ' in App (file://ReactFlightDOMNode-test.js:1432:25)',
1576 + ' in Dynamic (file://ReactFlightDOMNode-test.js:1423:27)\n' +
1577 + ' in App (file://ReactFlightDOMNode-test.js:1436:25)',
1578 );
1579 } else {
1580 expect(
@@ -1578,7 +1582,7 @@ describe('ReactFlightDOMNode', () => {
1582 ).toBe(
1583 '' +
1584 '\n' +
1581 - ' in App (file://ReactFlightDOMNode-test.js:1432:25)',
1585 + ' in App (file://ReactFlightDOMNode-test.js:1436:25)',
1586 );
1587 }
1588 } else {
@@ -1586,4 +1590,40 @@ describe('ReactFlightDOMNode', () => {
1590 }
1591 });
1592 });
1593 +
1594 + it('warns with a tailored message if eval is not available in dev', async () => {
1595 + // eslint-disable-next-line no-eval
1596 + const previousEval = globalThis.eval.bind(globalThis);
1597 + // eslint-disable-next-line no-eval
1598 + globalThis.eval = () => {
1599 + throw new Error('eval is disabled');
1600 + };
1601 +
1602 + try {
1603 + const readable = await serverAct(() =>
1604 + ReactServerDOMServer.renderToReadableStream({}, webpackMap),
1605 + );
1606 +
1607 + assertConsoleErrorDev([]);
1608 +
1609 + await ReactServerDOMClient.createFromReadableStream(readable, {
1610 + serverConsumerManifest: {
1611 + moduleMap: null,
1612 + moduleLoading: null,
1613 + },
1614 + });
1615 +
1616 + assertConsoleErrorDev([
1617 + 'eval() is not supported in this environment. ' +
1618 + 'This can happen if you started the Node.js process with --disallow-code-generation-from-strings, ' +
1619 + 'or if `eval` was patched by other means. ' +
1620 + 'React requires eval() in development mode for various debugging features ' +
1621 + 'like reconstructing callstacks from a different environment.\n' +
1622 + 'React will never use eval() in production mode',
1623 + ]);
1624 + } finally {
1625 + // eslint-disable-next-line no-eval
1626 + globalThis.eval = previousEval;
1627 + }
1628 + });
1629 });
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js
+31
@@ -10,6 +10,7 @@
10
11 'use strict';
12
13 +let assertConsoleErrorDev;
14 let serverExports;
15 let webpackServerMap;
16 let ReactServerDOMServer;
@@ -29,6 +30,9 @@ describe('ReactFlightDOMReplyEdge', () => {
30 ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
31 jest.resetModules();
32 ReactServerDOMClient = require('react-server-dom-webpack/client.edge');
33 +
34 + const InternalTestUtils = require('internal-test-utils');
35 + assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
36 });
37
38 it('can encode a reply', async () => {
@@ -373,4 +377,31 @@ describe('ReactFlightDOMReplyEdge', () => {
377 expect(replyResult.method).toBe(greet);
378 expect(replyResult.boundMethod()).toBe('hi, there');
379 });
380 +
381 + it('warns with a tailored message if eval is not available in dev', async () => {
382 + // eslint-disable-next-line no-eval
383 + const previousEval = globalThis.eval.bind(globalThis);
384 + // eslint-disable-next-line no-eval
385 + globalThis.eval = () => {
386 + throw new Error('eval is disabled');
387 + };
388 +
389 + try {
390 + const body = await ReactServerDOMClient.encodeReply({some: 'object'});
391 +
392 + assertConsoleErrorDev([
393 + 'eval() is not supported in this environment. ' +
394 + 'React requires eval() in development mode for various debugging features ' +
395 + 'like reconstructing callstacks from a different environment.\n' +
396 + 'React will never use eval() in production mode',
397 + ]);
398 +
399 + await ReactServerDOMServer.decodeReply(body, webpackServerMap);
400 +
401 + assertConsoleErrorDev([]);
402 + } finally {
403 + // eslint-disable-next-line no-eval
404 + globalThis.eval = previousEval;
405 + }
406 + });
407 });