From 9463d51e515544bb3119dd4cec46ce2177cf4c62 Mon Sep 17 00:00:00 2001
From: Andrew Clark
Date: Thu, 19 Dec 2024 10:18:06 -0500
Subject: [PATCH 1/6] Update runtime workflow to use HEAD commit (#31850)
This updates the CI workflow for the runtime build and tests to use the
HEAD commit of the PR branch rather than the Fake News merge commit that
the `@actions/checkout` action bafflingly defaults to.
Testing against the merge commit never made sense to me as a behavior
because as soon as someone updates upstream, it's out of date anyway.
It should just match the exact commit that the developer pushed, and the
once that appears in the GitHub UI.
---
.github/workflows/runtime_build_and_test.yml | 32 ++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml
index a14b5b376ce5e..d8657c3e98a58 100644
--- a/.github/workflows/runtime_build_and_test.yml
+++ b/.github/workflows/runtime_build_and_test.yml
@@ -25,6 +25,8 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.result }}
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/github-script@v7
id: set-matrix
with:
@@ -42,6 +44,8 @@ jobs:
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -64,6 +68,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -88,6 +94,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -139,6 +147,8 @@ jobs:
continue-on-error: true
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -166,6 +176,8 @@ jobs:
release_channel: [stable, experimental]
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -242,6 +254,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -272,6 +286,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -317,6 +333,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -350,6 +368,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -380,6 +400,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -419,6 +441,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -480,6 +504,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -528,6 +554,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -562,6 +590,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -603,6 +633,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
From bd76ce54d93153cc84d861bd59e906bc28e1c160 Mon Sep 17 00:00:00 2001
From: Jack Pope
Date: Thu, 19 Dec 2024 11:49:14 -0500
Subject: [PATCH 2/6] Fork Scheduler feature flags for native-fb (#31859)
#31787 introduces an experimental scheduler flag:
`enableAlwaysYieldScheduler`, which is turned off for www. There wasn't
a SchedulerFeatureFlags fork for native-fb, so the experimental change
was enabled in the Scheduler-dev build there which causes test failures
and is blocking the sync.
#31805 introduces another scheduler flag `enableRequestPaint`, which is
set as a `__VARIANT__` on www. I've set this to `true` here to preserve
the existing behavior. We can follow up with dynamic flags for native-fb
after unblocking the sync.
---
.../forks/SchedulerFeatureFlags.native-fb.js | 18 ++++++++++++++++++
scripts/rollup/forks.js | 18 +++++++++++-------
2 files changed, 29 insertions(+), 7 deletions(-)
create mode 100644 packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js
diff --git a/packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js b/packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js
new file mode 100644
index 0000000000000..d9d3dc733860f
--- /dev/null
+++ b/packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js
@@ -0,0 +1,18 @@
+/**
+ * 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
+ */
+
+export const enableProfiling = __DEV__;
+export const frameYieldMs = 5;
+
+export const userBlockingPriorityTimeout = 250;
+export const normalPriorityTimeout = 5000;
+export const lowPriorityTimeout = 10000;
+export const enableRequestPaint = true;
+
+export const enableAlwaysYieldScheduler = false;
diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js
index 2d6f718c76360..5460304366253 100644
--- a/scripts/rollup/forks.js
+++ b/scripts/rollup/forks.js
@@ -195,14 +195,18 @@ const forks = Object.freeze({
entry,
dependencies
) => {
- if (
- bundleType === FB_WWW_DEV ||
- bundleType === FB_WWW_PROD ||
- bundleType === FB_WWW_PROFILING
- ) {
- return './packages/scheduler/src/forks/SchedulerFeatureFlags.www.js';
+ switch (bundleType) {
+ case FB_WWW_DEV:
+ case FB_WWW_PROD:
+ case FB_WWW_PROFILING:
+ return './packages/scheduler/src/forks/SchedulerFeatureFlags.www.js';
+ case RN_FB_DEV:
+ case RN_FB_PROD:
+ case RN_FB_PROFILING:
+ return './packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js';
+ default:
+ return './packages/scheduler/src/SchedulerFeatureFlags.js';
}
- return './packages/scheduler/src/SchedulerFeatureFlags.js';
},
'./packages/shared/consoleWithStackDev.js': (bundleType, entry) => {
From 8f92ea467e2672a436e3e032299d5230d03187ed Mon Sep 17 00:00:00 2001
From: Ricky
Date: Thu, 19 Dec 2024 11:50:05 -0500
Subject: [PATCH 3/6] [assert helpers] forwardRef-test (#31843)
Starting to convert the rest of tests to the `assertConsoleTypeDev`
helpers.
---
packages/internal-test-utils/consoleMock.js | 1 +
.../react/src/__tests__/forwardRef-test.js | 104 ++++++++++--------
2 files changed, 60 insertions(+), 45 deletions(-)
diff --git a/packages/internal-test-utils/consoleMock.js b/packages/internal-test-utils/consoleMock.js
index 6d03c74c1dfee..561004a4dea76 100644
--- a/packages/internal-test-utils/consoleMock.js
+++ b/packages/internal-test-utils/consoleMock.js
@@ -273,6 +273,7 @@ function normalizeCodeLocInfo(str) {
// We strip that out in our normalization to make it look more like component stacks.
name = name.slice(0, name.length - 7);
}
+ name = name.replace(/.*\/([^\/]+):\d+:\d+/, '**/$1:**:**');
return '\n in ' + name + ' (at **)';
});
}
diff --git a/packages/react/src/__tests__/forwardRef-test.js b/packages/react/src/__tests__/forwardRef-test.js
index f0f3bb4d5023d..e08990c806355 100644
--- a/packages/react/src/__tests__/forwardRef-test.js
+++ b/packages/react/src/__tests__/forwardRef-test.js
@@ -13,6 +13,7 @@ describe('forwardRef', () => {
let React;
let ReactNoop;
let waitForAll;
+ let assertConsoleErrorDev;
beforeEach(() => {
jest.resetModules();
@@ -21,6 +22,7 @@ describe('forwardRef', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
it('should update refs when switching between children', async () => {
@@ -114,25 +116,31 @@ describe('forwardRef', () => {
});
it('should warn if not provided a callback during creation', () => {
- expect(() => React.forwardRef(undefined)).toErrorDev(
- 'forwardRef requires a render function but was given undefined.',
+ React.forwardRef(undefined);
+ assertConsoleErrorDev(
+ ['forwardRef requires a render function but was given undefined.'],
{withoutStack: true},
);
- expect(() => React.forwardRef(null)).toErrorDev(
- 'forwardRef requires a render function but was given null.',
+
+ React.forwardRef(null);
+ assertConsoleErrorDev(
+ ['forwardRef requires a render function but was given null.'],
{
withoutStack: true,
},
);
- expect(() => React.forwardRef('foo')).toErrorDev(
- 'forwardRef requires a render function but was given string.',
+
+ React.forwardRef('foo');
+ assertConsoleErrorDev(
+ ['forwardRef requires a render function but was given string.'],
{withoutStack: true},
);
});
it('should warn if no render function is provided', () => {
- expect(React.forwardRef).toErrorDev(
- 'forwardRef requires a render function but was given undefined.',
+ React.forwardRef();
+ assertConsoleErrorDev(
+ ['forwardRef requires a render function but was given undefined.'],
{withoutStack: true},
);
});
@@ -143,9 +151,12 @@ describe('forwardRef', () => {
}
renderWithDefaultProps.defaultProps = {};
- expect(() => React.forwardRef(renderWithDefaultProps)).toErrorDev(
- 'forwardRef render functions do not support defaultProps. ' +
- 'Did you accidentally pass a React component?',
+ React.forwardRef(renderWithDefaultProps);
+ assertConsoleErrorDev(
+ [
+ 'forwardRef render functions do not support defaultProps. ' +
+ 'Did you accidentally pass a React component?',
+ ],
{withoutStack: true},
);
});
@@ -159,9 +170,12 @@ describe('forwardRef', () => {
it('should warn if the render function provided does not use the forwarded ref parameter', () => {
const arityOfOne = props => ;
- expect(() => React.forwardRef(arityOfOne)).toErrorDev(
- 'forwardRef render functions accept exactly two parameters: props and ref. ' +
- 'Did you forget to use the ref parameter?',
+ React.forwardRef(arityOfOne);
+ assertConsoleErrorDev(
+ [
+ 'forwardRef render functions accept exactly two parameters: props and ref. ' +
+ 'Did you forget to use the ref parameter?',
+ ],
{withoutStack: true},
);
});
@@ -174,9 +188,12 @@ describe('forwardRef', () => {
it('should warn if the render function provided expects to use more than two parameters', () => {
const arityOfThree = (props, ref, x) => ;
- expect(() => React.forwardRef(arityOfThree)).toErrorDev(
- 'forwardRef render functions accept exactly two parameters: props and ref. ' +
- 'Any additional parameter will be undefined.',
+ React.forwardRef(arityOfThree);
+ assertConsoleErrorDev(
+ [
+ 'forwardRef render functions accept exactly two parameters: props and ref. ' +
+ 'Any additional parameter will be undefined.',
+ ],
{withoutStack: true},
);
});
@@ -190,15 +207,16 @@ describe('forwardRef', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from ForwardRef. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
- ' in ',
- );
+ (gate(flags => flags.enableOwnerStacks)
+ ? ' in **/forwardRef-test.js:**:** (at **)'
+ : ' in p (at **)'),
+ ]);
});
it('should use the inner function name for the stack', async () => {
@@ -210,16 +228,16 @@ describe('forwardRef', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from ForwardRef(Inner). ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Inner (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
it('should use the inner name in the stack', async () => {
@@ -233,16 +251,15 @@ describe('forwardRef', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from ForwardRef(Inner). ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Inner (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
it('can use the outer displayName in the stack', async () => {
@@ -255,16 +272,15 @@ describe('forwardRef', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from Outer. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Outer (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
it('should prefer the inner name to the outer displayName in the stack', async () => {
@@ -279,16 +295,15 @@ describe('forwardRef', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from Outer. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Inner (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
it('should not bailout if forwardRef is not wrapped in memo', async () => {
@@ -419,13 +434,12 @@ describe('forwardRef', () => {
});
it('warns on forwardRef(memo(...))', () => {
- expect(() => {
- React.forwardRef(
- React.memo((props, ref) => {
- return null;
- }),
- );
- }).toErrorDev(
+ React.forwardRef(
+ React.memo((props, ref) => {
+ return null;
+ }),
+ );
+ assertConsoleErrorDev(
[
'forwardRef requires a render function but received a `memo` ' +
'component. Instead of forwardRef(memo(...)), use ' +
From 9f540fcc51eae6fb6eab8d4ccba00cb0477a6b7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Thu, 19 Dec 2024 12:54:59 -0500
Subject: [PATCH 4/6] [Flight] Support streaming of decodeReply in Edge
environments (#31852)
We support streaming `multipart/form-data` in Node.js using Busboy since
that's kind of the idiomatic ecosystem way for handling these stream
there. There's not really anything idiomatic like that for Edge that's
universal yet.
This adds a version that's basically just
`AsyncIterable.from(formData)`. It could also be a `ReadableStream` of
those entries since those are also `AsyncIterable`.
I imagine that in the future we might add one from a binary
`ReadableStream` that does the parsing built-in.
---
.../npm/server.edge.js | 1 +
.../react-server-dom-parcel/server.edge.js | 1 +
.../src/server/ReactFlightDOMServerEdge.js | 49 ++++++++++++++++++
.../server/react-flight-dom-server.edge.js | 1 +
.../npm/server.edge.js | 1 +
.../react-server-dom-turbopack/server.edge.js | 1 +
.../src/server/ReactFlightDOMServerEdge.js | 51 +++++++++++++++++++
.../server/react-flight-dom-server.edge.js | 1 +
.../npm/server.edge.js | 1 +
.../react-server-dom-webpack/server.edge.js | 1 +
.../__tests__/ReactFlightDOMReplyEdge-test.js | 36 +++++++++++++
.../src/server/ReactFlightDOMServerEdge.js | 51 +++++++++++++++++++
.../server/react-flight-dom-server.edge.js | 1 +
13 files changed, 196 insertions(+)
diff --git a/packages/react-server-dom-parcel/npm/server.edge.js b/packages/react-server-dom-parcel/npm/server.edge.js
index 5f13279f75e2b..356cce93a70bc 100644
--- a/packages/react-server-dom-parcel/npm/server.edge.js
+++ b/packages/react-server-dom-parcel/npm/server.edge.js
@@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') {
exports.renderToReadableStream = s.renderToReadableStream;
exports.decodeReply = s.decodeReply;
+exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
exports.decodeAction = s.decodeAction;
exports.decodeFormState = s.decodeFormState;
exports.createClientReference = s.createClientReference;
diff --git a/packages/react-server-dom-parcel/server.edge.js b/packages/react-server-dom-parcel/server.edge.js
index 0974db3448fb7..42f5c3d65399c 100644
--- a/packages/react-server-dom-parcel/server.edge.js
+++ b/packages/react-server-dom-parcel/server.edge.js
@@ -10,6 +10,7 @@
export {
renderToReadableStream,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
createClientReference,
diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js
index 73a8741618213..2a365993a7cfb 100644
--- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js
+++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js
@@ -17,6 +17,8 @@ import {
type ServerReferenceId,
} from '../client/ReactFlightClientConfigBundlerParcel';
+import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
+
import {
createRequest,
createPrerenderRequest,
@@ -30,6 +32,9 @@ import {
createResponse,
close,
getRoot,
+ reportGlobalError,
+ resolveField,
+ resolveFile,
} from 'react-server/src/ReactFlightReplyServer';
import {
@@ -189,6 +194,50 @@ export function decodeReply(
return root;
}
+export function decodeReplyFromAsyncIterable(
+ iterable: AsyncIterable<[string, string | File]>,
+ options?: {temporaryReferences?: TemporaryReferenceSet},
+): Thenable {
+ const iterator: AsyncIterator<[string, string | File]> =
+ iterable[ASYNC_ITERATOR]();
+
+ const response = createResponse(
+ serverManifest,
+ '',
+ options ? options.temporaryReferences : undefined,
+ );
+
+ function progress(
+ entry:
+ | {done: false, +value: [string, string | File], ...}
+ | {done: true, +value: void, ...},
+ ) {
+ if (entry.done) {
+ close(response);
+ } else {
+ const [name, value] = entry.value;
+ if (typeof value === 'string') {
+ resolveField(response, name, value);
+ } else {
+ resolveFile(response, name, value);
+ }
+ iterator.next().then(progress, error);
+ }
+ }
+ function error(reason: Error) {
+ reportGlobalError(response, reason);
+ if (typeof (iterator: any).throw === 'function') {
+ // The iterator protocol doesn't necessarily include this but a generator do.
+ // $FlowFixMe should be able to pass mixed
+ iterator.throw(reason).then(error, error);
+ }
+ }
+
+ iterator.next().then(progress, error);
+
+ return getRoot(response);
+}
+
export function decodeAction(body: FormData): Promise<() => T> | null {
return decodeActionImpl(body, serverManifest);
}
diff --git a/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js
index c6b3067fbcfdb..54f3dbb2ec346 100644
--- a/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js
+++ b/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js
@@ -11,6 +11,7 @@ export {
renderToReadableStream,
prerender as unstable_prerender,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
createClientReference,
diff --git a/packages/react-server-dom-turbopack/npm/server.edge.js b/packages/react-server-dom-turbopack/npm/server.edge.js
index e34b18fa0156a..c832080079dd5 100644
--- a/packages/react-server-dom-turbopack/npm/server.edge.js
+++ b/packages/react-server-dom-turbopack/npm/server.edge.js
@@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') {
exports.renderToReadableStream = s.renderToReadableStream;
exports.decodeReply = s.decodeReply;
+exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
exports.decodeAction = s.decodeAction;
exports.decodeFormState = s.decodeFormState;
exports.registerServerReference = s.registerServerReference;
diff --git a/packages/react-server-dom-turbopack/server.edge.js b/packages/react-server-dom-turbopack/server.edge.js
index c527c7f76a74f..8f0347cd7b5a7 100644
--- a/packages/react-server-dom-turbopack/server.edge.js
+++ b/packages/react-server-dom-turbopack/server.edge.js
@@ -10,6 +10,7 @@
export {
renderToReadableStream,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
registerServerReference,
diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js
index 11dbe1a7c1358..e8256767fa5b1 100644
--- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js
+++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js
@@ -12,6 +12,8 @@ import type {Thenable} from 'shared/ReactTypes';
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
+import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
+
import {
createRequest,
createPrerenderRequest,
@@ -25,6 +27,9 @@ import {
createResponse,
close,
getRoot,
+ reportGlobalError,
+ resolveField,
+ resolveFile,
} from 'react-server/src/ReactFlightReplyServer';
import {
@@ -183,10 +188,56 @@ function decodeReply(
return root;
}
+function decodeReplyFromAsyncIterable(
+ iterable: AsyncIterable<[string, string | File]>,
+ turbopackMap: ServerManifest,
+ options?: {temporaryReferences?: TemporaryReferenceSet},
+): Thenable {
+ const iterator: AsyncIterator<[string, string | File]> =
+ iterable[ASYNC_ITERATOR]();
+
+ const response = createResponse(
+ turbopackMap,
+ '',
+ options ? options.temporaryReferences : undefined,
+ );
+
+ function progress(
+ entry:
+ | {done: false, +value: [string, string | File], ...}
+ | {done: true, +value: void, ...},
+ ) {
+ if (entry.done) {
+ close(response);
+ } else {
+ const [name, value] = entry.value;
+ if (typeof value === 'string') {
+ resolveField(response, name, value);
+ } else {
+ resolveFile(response, name, value);
+ }
+ iterator.next().then(progress, error);
+ }
+ }
+ function error(reason: Error) {
+ reportGlobalError(response, reason);
+ if (typeof (iterator: any).throw === 'function') {
+ // The iterator protocol doesn't necessarily include this but a generator do.
+ // $FlowFixMe should be able to pass mixed
+ iterator.throw(reason).then(error, error);
+ }
+ }
+
+ iterator.next().then(progress, error);
+
+ return getRoot(response);
+}
+
export {
renderToReadableStream,
prerender,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
};
diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js
index 48c4fc4553e6b..9198f9913ed37 100644
--- a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js
+++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js
@@ -11,6 +11,7 @@ export {
renderToReadableStream,
prerender as unstable_prerender,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
registerServerReference,
diff --git a/packages/react-server-dom-webpack/npm/server.edge.js b/packages/react-server-dom-webpack/npm/server.edge.js
index 591b84476884d..51a58ea7a9e30 100644
--- a/packages/react-server-dom-webpack/npm/server.edge.js
+++ b/packages/react-server-dom-webpack/npm/server.edge.js
@@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') {
exports.renderToReadableStream = s.renderToReadableStream;
exports.decodeReply = s.decodeReply;
+exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
exports.decodeAction = s.decodeAction;
exports.decodeFormState = s.decodeFormState;
exports.registerServerReference = s.registerServerReference;
diff --git a/packages/react-server-dom-webpack/server.edge.js b/packages/react-server-dom-webpack/server.edge.js
index c527c7f76a74f..8f0347cd7b5a7 100644
--- a/packages/react-server-dom-webpack/server.edge.js
+++ b/packages/react-server-dom-webpack/server.edge.js
@@ -10,6 +10,7 @@
export {
renderToReadableStream,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
registerServerReference,
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js
index f6157dff171d7..2effa9868e99b 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js
@@ -272,4 +272,40 @@ describe('ReactFlightDOMReplyEdge', () => {
expect(error).not.toBe(null);
expect(error.message).toBe('Connection closed.');
});
+
+ it('can stream the decoding using an async iterable', async () => {
+ let resolve;
+ const promise = new Promise(r => (resolve = r));
+
+ const buffer = new Uint8Array([
+ 123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
+ ]);
+
+ const formData = await ReactServerDOMClient.encodeReply({
+ a: Promise.resolve('hello'),
+ b: Promise.resolve(buffer),
+ });
+
+ const iterable = {
+ async *[Symbol.asyncIterator]() {
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+ for (const entry of formData) {
+ yield entry;
+ await promise;
+ }
+ },
+ };
+
+ const decoded = await ReactServerDOMServer.decodeReplyFromAsyncIterable(
+ iterable,
+ webpackServerMap,
+ );
+
+ expect(Object.keys(decoded)).toEqual(['a', 'b']);
+
+ await resolve();
+
+ expect(await decoded.a).toBe('hello');
+ expect(Array.from(await decoded.b)).toEqual(Array.from(buffer));
+ });
});
diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js
index 7954417b95a25..e5b834be0543f 100644
--- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js
+++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js
@@ -12,6 +12,8 @@ import type {Thenable} from 'shared/ReactTypes';
import type {ClientManifest} from './ReactFlightServerConfigWebpackBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
+import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
+
import {
createRequest,
createPrerenderRequest,
@@ -25,6 +27,9 @@ import {
createResponse,
close,
getRoot,
+ reportGlobalError,
+ resolveField,
+ resolveFile,
} from 'react-server/src/ReactFlightReplyServer';
import {
@@ -183,10 +188,56 @@ function decodeReply(
return root;
}
+function decodeReplyFromAsyncIterable(
+ iterable: AsyncIterable<[string, string | File]>,
+ webpackMap: ServerManifest,
+ options?: {temporaryReferences?: TemporaryReferenceSet},
+): Thenable {
+ const iterator: AsyncIterator<[string, string | File]> =
+ iterable[ASYNC_ITERATOR]();
+
+ const response = createResponse(
+ webpackMap,
+ '',
+ options ? options.temporaryReferences : undefined,
+ );
+
+ function progress(
+ entry:
+ | {done: false, +value: [string, string | File], ...}
+ | {done: true, +value: void, ...},
+ ) {
+ if (entry.done) {
+ close(response);
+ } else {
+ const [name, value] = entry.value;
+ if (typeof value === 'string') {
+ resolveField(response, name, value);
+ } else {
+ resolveFile(response, name, value);
+ }
+ iterator.next().then(progress, error);
+ }
+ }
+ function error(reason: Error) {
+ reportGlobalError(response, reason);
+ if (typeof (iterator: any).throw === 'function') {
+ // The iterator protocol doesn't necessarily include this but a generator do.
+ // $FlowFixMe should be able to pass mixed
+ iterator.throw(reason).then(error, error);
+ }
+ }
+
+ iterator.next().then(progress, error);
+
+ return getRoot(response);
+}
+
export {
renderToReadableStream,
prerender,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
};
diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js
index 48c4fc4553e6b..9198f9913ed37 100644
--- a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js
+++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js
@@ -11,6 +11,7 @@ export {
renderToReadableStream,
prerender as unstable_prerender,
decodeReply,
+ decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
registerServerReference,
From c70ab3f4b051348e3dd91144d9c7299a2e2311a5 Mon Sep 17 00:00:00 2001
From: lauren
Date: Thu, 19 Dec 2024 13:03:11 -0500
Subject: [PATCH 5/6] [ci] getWorkflowRun should not throw early if workflow
hasn't completed (#31861)
We already have handling and retry logic for in-flight workflows in
`downloadArtifactsFromGitHub`, so there's no need to exit early if we
find a workflow for a given commit but it hasn't finished yet.
---
.../release/shared-commands/download-build-artifacts.js | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/scripts/release/shared-commands/download-build-artifacts.js b/scripts/release/shared-commands/download-build-artifacts.js
index 85cbb8b9fa176..cf7301688027a 100644
--- a/scripts/release/shared-commands/download-build-artifacts.js
+++ b/scripts/release/shared-commands/download-build-artifacts.js
@@ -43,12 +43,7 @@ async function getWorkflowRun(commit) {
);
const json = JSON.parse(res.stdout);
- const workflowRun = json.workflow_runs.find(
- run =>
- run.head_sha === commit &&
- run.status === 'completed' &&
- run.conclusion === 'success'
- );
+ const workflowRun = json.workflow_runs.find(run => run.head_sha === commit);
if (workflowRun == null || workflowRun.id == null) {
console.log(
From 36d15d58628baf5e15624a52febae873a7a56345 Mon Sep 17 00:00:00 2001
From: Ricky
Date: Thu, 19 Dec 2024 13:05:23 -0500
Subject: [PATCH 6/6] [assert helpers] ReactChildren-test (#31844)
Based off https://github.com/facebook/react/pull/31843
Commit to review:
https://github.com/facebook/react/pull/31844/commits/2c653b81a73e155f1548c0362e5334629a45351e
Moar tests
---
.../react/src/__tests__/ReactChildren-test.js | 206 +++++++++++-------
1 file changed, 122 insertions(+), 84 deletions(-)
diff --git a/packages/react/src/__tests__/ReactChildren-test.js b/packages/react/src/__tests__/ReactChildren-test.js
index 2723755abfe4a..5cce6b24873f0 100644
--- a/packages/react/src/__tests__/ReactChildren-test.js
+++ b/packages/react/src/__tests__/ReactChildren-test.js
@@ -13,12 +13,13 @@ describe('ReactChildren', () => {
let React;
let ReactDOMClient;
let act;
+ let assertConsoleErrorDev;
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOMClient = require('react-dom/client');
- act = require('internal-test-utils').act;
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
});
it('should support identity for simple', () => {
@@ -331,14 +332,16 @@ describe('ReactChildren', () => {
callback.mockClear();
}
- let instance;
- expect(() => {
- instance = {threeDivIterable}
;
- }).toErrorDev(
+ const instance = {threeDivIterable}
;
+ assertConsoleErrorDev(
// With the flag on this doesn't warn eagerly but only when rendered
gate(flag => flag.enableOwnerStacks)
? []
- : ['Each child in a list should have a unique "key" prop.'],
+ : [
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the top-level render call using . See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)',
+ ],
);
React.Children.forEach(instance.props.children, callback, context);
@@ -359,11 +362,16 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render(instance);
- });
- }).toErrorDev('Each child in a list should have a unique "key" prop.');
+ await act(() => {
+ root.render(instance);
+ });
+ assertConsoleErrorDev([
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the top-level render call using
. It was passed a child from div.' +
+ ' See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)' +
+ (gate(flag => flag.enableOwnerStacks) ? '' : '\n in div (at **)'),
+ ]);
});
it('should be called for each child in an iterable with keys', () => {
@@ -879,15 +887,29 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render(
-
- {[]}
- ,
- );
- });
- }).toErrorDev(['Each child in a list should have a unique "key" prop.']);
+ await act(() => {
+ root.render(
+
+ {[]}
+ ,
+ );
+ });
+ assertConsoleErrorDev(
+ gate(flags => flags.enableOwnerStacks)
+ ? [
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the render method of `ComponentRenderingMappedChildren`.' +
+ ' See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)\n' +
+ ' in **/ReactChildren-test.js:**:** (at **)',
+ ]
+ : [
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the top-level render call using
.' +
+ ' See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)',
+ ],
+ );
});
it('does not warn for mapped static children without keys', async () => {
@@ -903,16 +925,14 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render(
-
-
-
- ,
- );
- });
- }).toErrorDev([]);
+ await act(() => {
+ root.render(
+
+
+
+ ,
+ );
+ });
});
it('warns for cloned list children without keys', async () => {
@@ -926,15 +946,28 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render(
-
- {[]}
- ,
- );
- });
- }).toErrorDev(['Each child in a list should have a unique "key" prop.']);
+ await act(() => {
+ root.render(
+
+ {[]}
+ ,
+ );
+ });
+ assertConsoleErrorDev(
+ gate(flags => flags.enableOwnerStacks)
+ ? [
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the render method of `ComponentRenderingClonedChildren`.' +
+ ' See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)',
+ ]
+ : [
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the top-level render call using .' +
+ ' See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)',
+ ],
+ );
});
it('does not warn for cloned static children without keys', async () => {
@@ -948,16 +981,14 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render(
-
-
-
- ,
- );
- });
- }).toErrorDev([]);
+ await act(() => {
+ root.render(
+
+
+
+ ,
+ );
+ });
});
it('warns for flattened list children without keys', async () => {
@@ -967,15 +998,28 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render(
-
- {[]}
- ,
- );
- });
- }).toErrorDev(['Each child in a list should have a unique "key" prop.']);
+ await act(() => {
+ root.render(
+
+ {[]}
+ ,
+ );
+ });
+ assertConsoleErrorDev(
+ gate(flags => flags.enableOwnerStacks)
+ ? [
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the render method of `ComponentRenderingFlattenedChildren`.' +
+ ' See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)',
+ ]
+ : [
+ 'Each child in a list should have a unique "key" prop.\n\n' +
+ 'Check the top-level render call using .' +
+ ' See https://react.dev/link/warning-keys for more information.\n' +
+ ' in div (at **)',
+ ],
+ );
});
it('does not warn for flattened static children without keys', async () => {
@@ -985,16 +1029,14 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render(
-
-
-
- ,
- );
- });
- }).toErrorDev([]);
+ await act(() => {
+ root.render(
+
+
+
+ ,
+ );
+ });
});
it('should escape keys', () => {
@@ -1153,18 +1195,16 @@ describe('ReactChildren', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render();
- });
- }).toErrorDev(
- '' +
- 'Each child in a list should have a unique "key" prop.' +
+ await act(() => {
+ root.render();
+ });
+ assertConsoleErrorDev([
+ 'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from ComponentReturningArray. ' +
'See https://react.dev/link/warning-keys for more information.' +
'\n in div (at **)' +
'\n in ComponentReturningArray (at **)',
- );
+ ]);
});
it('does not warn when there are keys on elements in a fragment', async () => {
@@ -1184,17 +1224,15 @@ describe('ReactChildren', () => {
it('warns for keys for arrays at the top level', async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- root.render([, ]);
- });
- }).toErrorDev(
- '' +
- 'Each child in a list should have a unique "key" prop.' +
+ await act(() => {
+ root.render([, ]);
+ });
+ assertConsoleErrorDev([
+ 'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . ' +
'See https://react.dev/link/warning-keys for more information.' +
'\n in div (at **)',
- );
+ ]);
});
});
});