Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/react-client/src/ReactClientDebugConfigBrowser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

let hasConfirmedEval = false;
export function checkEvalAvailabilityOnceDev(): void {
if (__DEV__) {
if (!hasConfirmedEval) {
hasConfirmedEval = true;
try {
// eslint-disable-next-line no-eval
(0, eval)('null');
} catch {
console.error(
'eval() is not supported in this environment. ' +
'If this page was served with a `Content-Security-Policy` header, ' +
'make sure that `unsafe-eval` is included. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
);
}
Comment on lines +18 to +27

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If eval throws for reasons other than being unavailable, those errors will be swallowed.

Suggested change
} catch {
console.error(
'eval() is not supported in this environment. ' +
'If this page was served with a `Content-Security-Policy` header, ' +
'make sure that `unsafe-eval` is included. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
);
}
} catch (error) {
console.error(
'eval() is not supported in this environment. ' +
'If this page was served with a `Content-Security-Policy` header, ' +
'make sure that `unsafe-eval` is included. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
,error
);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the thrown error saying, that's not included in the message we post?

}
} else {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
);
}
}
36 changes: 36 additions & 0 deletions packages/react-client/src/ReactClientDebugConfigNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

let hasConfirmedEval = false;
export function checkEvalAvailabilityOnceDev(): void {
if (__DEV__) {
if (!hasConfirmedEval) {
hasConfirmedEval = true;
try {
// eslint-disable-next-line no-eval
(0, eval)('null');
} catch {
console.error(
'eval() is not supported in this environment. ' +
'This can happen if you started the Node.js process with --disallow-code-generation-from-strings, ' +
'or if `eval` was patched by other means. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
);
}
}
} else {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
);
}
}
34 changes: 34 additions & 0 deletions packages/react-client/src/ReactClientDebugConfigPlain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

let hasConfirmedEval = false;
export function checkEvalAvailabilityOnceDev(): void {
if (__DEV__) {
if (!hasConfirmedEval) {
hasConfirmedEval = true;
try {
// eslint-disable-next-line no-eval
(0, eval)('null');
} catch {
console.error(
'eval() is not supported in this environment. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
);
}
}
} else {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
);
}
}
9 changes: 9 additions & 0 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
bindToConsole,
rendererVersion,
rendererPackageName,
checkEvalAvailabilityOnceDev,
} from './ReactFlightClientConfig';

import {
Expand Down Expand Up @@ -2768,6 +2769,14 @@ export function createResponse(
debugEndTime: void | number, // DEV-only
debugChannel: void | DebugChannel, // DEV-only
): WeakResponse {
if (__DEV__) {
// We use eval to create fake function stacks which includes Component stacks.
// A warning would be noise if you used Flight without Components and don't encounter
// errors. We're warning eagerly so that you configure your environment accordingly
// before you encounter an error.
checkEvalAvailabilityOnceDev();
}

return getWeakResponse(
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
new ResponseInstance(
Expand Down
13 changes: 12 additions & 1 deletion packages/react-client/src/ReactFlightReplyClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ import getPrototypeOf from 'shared/getPrototypeOf';

const ObjectPrototype = Object.prototype;

import {usedWithSSR} from './ReactFlightClientConfig';
import {
usedWithSSR,
checkEvalAvailabilityOnceDev,
} from './ReactFlightClientConfig';

type ReactJSONValue =
| string
Expand Down Expand Up @@ -190,6 +193,14 @@ export function processReply(
const writtenObjects: WeakMap<Reference, string> = new WeakMap();
let modelRoot: null | ReactServerValue = root;

if (__DEV__) {
// We use eval to create fake function stacks which includes Component stacks.
// A warning would be noise if you used Flight without Components and don't encounter
// errors. We're warning eagerly so that you configure your environment accordingly
// before you encounter an error.
checkEvalAvailabilityOnceDev();
}

function serializeTypedArray(
tag: string,
typedArray: $ArrayBufferView,
Expand Down
6 changes: 6 additions & 0 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3715,6 +3715,12 @@ describe('ReactFlight', () => {
'\n in b (at **)' +
'\n in a (at **)',
);
assertConsoleErrorDev([
'eval() is not supported in this environment. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
]);
} else {
expect(receivedError.message).toEqual(
'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.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ export const bindToConsole = $$$config.bindToConsole;

export const rendererVersion = $$$config.rendererVersion;
export const rendererPackageName = $$$config.rendererPackageName;

export const checkEvalAvailabilityOnceDev =
$$$config.checkEvalAvailabilityOnceDev;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-esm';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
export * from 'react-client/src/ReactClientDebugConfigBrowser';
export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM';
export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigTargetESMBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-parcel';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
export * from 'react-client/src/ReactClientDebugConfigBrowser';
export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel';
export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigTargetParcelBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-turbopack';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
export * from 'react-client/src/ReactClientDebugConfigBrowser';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackBrowser';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-webpack';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
export * from 'react-client/src/ReactClientDebugConfigBrowser';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackBrowser';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-bun';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigPlain';
export * from 'react-client/src/ReactClientDebugConfigPlain';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

export opaque type ModuleLoading = mixed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-parcel';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigPlain';
export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel';
export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigTargetParcelServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-turbopack';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigPlain';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackServer';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-webpack';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigPlain';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'not-used';

export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
export * from 'react-client/src/ReactClientDebugConfigBrowser';

export type Response = any;
export opaque type ModuleLoading = mixed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-esm';

export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigNode';
export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM';
export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigTargetESMServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-parcel';

export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigNode';
export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel';
export * from 'react-server-dom-parcel/src/client/ReactFlightClientConfigTargetParcelServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-turbopack';

export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigNode';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer';
export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigTargetTurbopackServer';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rendererPackageName = 'react-server-dom-unbundled';

export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigNode';
export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode';
export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const rendererPackageName = 'react-server-dom-webpack';

export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-client/src/ReactClientDebugConfigNode';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {Thenable} from 'shared/ReactTypes';

export * from 'react-markup/src/ReactMarkupLegacyClientStreamConfig.js';
export * from 'react-client/src/ReactClientConsoleConfigPlain';
export * from 'react-client/src/ReactClientDebugConfigPlain';

export type ModuleLoading = null;
export type ServerConsumerModuleMap = null;
Expand Down
25 changes: 25 additions & 0 deletions packages/react-noop-renderer/src/ReactNoopFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const {createResponse, createStreamState, processBinaryChunk, getRoot, close} =
[console].concat(args),
);
},
checkEvalAvailabilityOnceDev,
});

type ReadOptions = {|
Expand Down Expand Up @@ -87,4 +88,28 @@ function read<T>(source: Source, options: ReadOptions): Thenable<T> {
return getRoot(response);
}

let hasConfirmedEval = false;
function checkEvalAvailabilityOnceDev(): void {
if (__DEV__) {
if (!hasConfirmedEval) {
hasConfirmedEval = true;
try {
// eslint-disable-next-line no-eval
(0, eval)('null');
} catch {
console.error(
'eval() is not supported in this environment. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
);
}
}
} else {
throw new Error(
'checkEvalAvailabilityOnceDev should never be called in production mode. This is a bug in React.',
);
}
}

export {read};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ global.TextEncoder = require('util').TextEncoder;
global.TextDecoder = require('util').TextDecoder;

// let serverExports;
let assertConsoleErrorDev;
let turbopackServerMap;
let ReactServerDOMServer;
let ReactServerDOMClient;
Expand All @@ -41,6 +42,9 @@ describe('ReactFlightTurbopackDOMReply', () => {
ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
jest.resetModules();
ReactServerDOMClient = require('react-server-dom-turbopack/client');

const InternalTestUtils = require('internal-test-utils');
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});

it('can encode a reply', async () => {
Expand All @@ -52,4 +56,32 @@ describe('ReactFlightTurbopackDOMReply', () => {

expect(decoded).toEqual({some: 'object'});
});

it('warns with a tailored message if eval is not available in dev', async () => {
// eslint-disable-next-line no-eval
const previousEval = globalThis.eval.bind(globalThis);
// eslint-disable-next-line no-eval
globalThis.eval = () => {
throw new Error('eval is disabled');
};

try {
const body = await ReactServerDOMClient.encodeReply({some: 'object'});
assertConsoleErrorDev([
'eval() is not supported in this environment. ' +
'If this page was served with a `Content-Security-Policy` header, ' +
'make sure that `unsafe-eval` is included. ' +
'React requires eval() in development mode for various debugging features ' +
'like reconstructing callstacks from a different environment.\n' +
'React will never use eval() in production mode',
]);

await ReactServerDOMServer.decodeReply(body, turbopackServerMap);

assertConsoleErrorDev([]);
} finally {
// eslint-disable-next-line no-eval
globalThis.eval = previousEval;
}
});
});
Loading
Loading