Compare commits

...

3 Commits

Author SHA1 Message Date
Sebastian Markbage
bbed0b0ee6 Bring ReactFlightClient fixes to FlightReplyServer
We did a bunch of refactors to ReactFlightClient in PRs like #29823 and #33664.

This brings a bunch of those related refactors to the equivalent FlightReplyServer. Such as deep resolution of cycles and deferred error handling.
2025-12-03 10:00:46 +01:00
Sebastian Sebbie Silbermann
f3e3f6fbac Version 19.0.1 2025-12-02 23:12:51 +01:00
Sebastian "Sebbie" Silbermann
154f4b15e2 [release] Allow building single release channel with processed versions (#35270) 2025-12-02 22:54:07 +01:00
23 changed files with 737 additions and 311 deletions

View File

@@ -7,18 +7,18 @@
//
// The @latest channel uses the version as-is, e.g.:
//
// 19.0.0
// 19.0.1
//
// The @canary channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 19.0.0-canary-a1c2d3e4
// 19.0.1-canary-a1c2d3e4
//
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.0.0';
const ReactVersion = '19.0.1';
// The label used by the @canary channel. Represents the upcoming release's
// stability. Most of the time, this will be "canary", but we may temporarily

View File

@@ -111,7 +111,7 @@
},
"scripts": {
"build": "node ./scripts/rollup/build-all-release-channels.js",
"build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE",
"build-for-devtools": "cross-env yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE --release-channel=experimental",
"build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
"build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
"build-for-flight-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/ --type=NODE_DEV,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",

View File

@@ -20,8 +20,8 @@
"homepage": "https://react.dev/",
"peerDependencies": {
"jest": "^23.0.1 || ^24.0.0 || ^25.1.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0",
"react": "^19.0.0",
"react-test-renderer": "^19.0.0"
"react": "^19.0.1",
"react-test-renderer": "^19.0.1"
},
"files": [
"LICENSE",

View File

@@ -1,7 +1,7 @@
{
"name": "react-art",
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
"version": "19.0.0",
"version": "19.0.1",
"main": "index.js",
"repository": {
"type": "git",
@@ -27,7 +27,7 @@
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^19.0.0"
"react": "^19.0.1"
},
"files": [
"LICENSE",

View File

@@ -1,7 +1,7 @@
{
"name": "react-dom-bindings",
"description": "React implementation details for react-dom.",
"version": "19.0.0",
"version": "19.0.1",
"private": true,
"main": "index.js",
"repository": {
@@ -18,6 +18,6 @@
},
"homepage": "https://react.dev/",
"peerDependencies": {
"react": "^19.0.0"
"react": "^19.0.1"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "react-dom",
"version": "19.0.0",
"version": "19.0.1",
"description": "React package for working with the DOM.",
"main": "index.js",
"repository": {
@@ -20,7 +20,7 @@
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^19.0.0"
"react": "^19.0.1"
},
"files": [
"LICENSE",

View File

@@ -1,6 +1,6 @@
{
"name": "react-is",
"version": "19.0.0",
"version": "19.0.1",
"description": "Brand checking of React Elements.",
"main": "index.js",
"sideEffects": false,

View File

@@ -1,6 +1,6 @@
{
"name": "react-markup",
"version": "19.0.0",
"version": "19.0.1",
"description": "React package generating embedded markup such as e-mails with support for Server Components.",
"main": "index.js",
"repository": {
@@ -17,7 +17,7 @@
},
"homepage": "https://react.dev/",
"peerDependencies": {
"react": "^19.0.0"
"react": "^19.0.1"
},
"files": [
"LICENSE",

View File

@@ -26,7 +26,7 @@
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0"
"react": "^19.0.1"
},
"dependencies": {
"scheduler": "^0.23.0"

View File

@@ -1,7 +1,7 @@
{
"name": "react-server-dom-esm",
"description": "React Server Components bindings for DOM using ESM. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
"version": "19.0.0",
"version": "19.0.1",
"keywords": [
"react"
],
@@ -54,8 +54,8 @@
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react": "^19.0.1",
"react-dom": "^19.0.1"
},
"dependencies": {
"acorn-loose": "^8.3.0",

View File

@@ -222,16 +222,23 @@ function decodeReplyFromBusboy<T>(
// we queue any fields we receive until the previous file is done.
queuedFields.push(name, value);
} else {
resolveField(response, name, value);
try {
resolveField(response, name, value);
} catch (error) {
busboyStream.destroy(error);
}
}
});
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
if (encoding.toLowerCase() === 'base64') {
throw new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
busboyStream.destroy(
new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
),
);
return;
}
pendingFiles++;
const file = resolveFileInfo(response, name, filename, mimeType);
@@ -239,14 +246,18 @@ function decodeReplyFromBusboy<T>(
resolveFileChunk(response, file, chunk);
});
value.on('end', () => {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
try {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
}
queuedFields.length = 0;
}
queuedFields.length = 0;
} catch (error) {
busboyStream.destroy(error);
}
});
});

View File

@@ -1,7 +1,7 @@
{
"name": "react-server-dom-turbopack",
"description": "React Server Components bindings for DOM using Turbopack. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
"version": "19.0.0",
"version": "19.0.1",
"keywords": [
"react"
],
@@ -79,8 +79,8 @@
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react": "^19.0.1",
"react-dom": "^19.0.1"
},
"dependencies": {
"acorn-loose": "^8.3.0",

View File

@@ -30,6 +30,8 @@ import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientCo
import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
import hasOwnProperty from 'shared/hasOwnProperty';
export type ServerConsumerModuleMap = null | {
[clientId: string]: {
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -228,5 +230,8 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
// default property of this if it was an ESM interop module.
return moduleExports.__esModule ? moduleExports.default : moduleExports;
}
return moduleExports[metadata[NAME]];
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
return moduleExports[metadata[NAME]];
}
return (undefined: any);
}

View File

@@ -224,16 +224,23 @@ function decodeReplyFromBusboy<T>(
// we queue any fields we receive until the previous file is done.
queuedFields.push(name, value);
} else {
resolveField(response, name, value);
try {
resolveField(response, name, value);
} catch (error) {
busboyStream.destroy(error);
}
}
});
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
if (encoding.toLowerCase() === 'base64') {
throw new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
busboyStream.destroy(
new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
),
);
return;
}
pendingFiles++;
const file = resolveFileInfo(response, name, filename, mimeType);
@@ -241,14 +248,18 @@ function decodeReplyFromBusboy<T>(
resolveFileChunk(response, file, chunk);
});
value.on('end', () => {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
try {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
}
queuedFields.length = 0;
}
queuedFields.length = 0;
} catch (error) {
busboyStream.destroy(error);
}
});
});

View File

@@ -1,7 +1,7 @@
{
"name": "react-server-dom-webpack",
"description": "React Server Components bindings for DOM using Webpack. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
"version": "19.0.0",
"version": "19.0.1",
"keywords": [
"react"
],
@@ -100,8 +100,8 @@
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "^19.0.1",
"react-dom": "^19.0.1",
"webpack": "^5.59.0"
},
"dependencies": {

View File

@@ -24,6 +24,8 @@ import {
} from '../shared/ReactFlightImportMetadata';
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
import hasOwnProperty from 'shared/hasOwnProperty';
export type ServerConsumerModuleMap = {
[clientId: string]: {
[clientExportName: string]: ClientReference<any>,
@@ -158,5 +160,8 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
// default property of this if it was an ESM interop module.
return moduleExports.default;
}
return moduleExports[metadata.name];
if (hasOwnProperty.call(moduleExports, metadata.name)) {
return moduleExports[metadata.name];
}
return (undefined: any);
}

View File

@@ -30,6 +30,8 @@ import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientCo
import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
import hasOwnProperty from 'shared/hasOwnProperty';
export type ServerConsumerModuleMap = null | {
[clientId: string]: {
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -249,5 +251,8 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
// default property of this if it was an ESM interop module.
return moduleExports.__esModule ? moduleExports.default : moduleExports;
}
return moduleExports[metadata[NAME]];
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
return moduleExports[metadata[NAME]];
}
return (undefined: any);
}

View File

@@ -224,16 +224,23 @@ function decodeReplyFromBusboy<T>(
// we queue any fields we receive until the previous file is done.
queuedFields.push(name, value);
} else {
resolveField(response, name, value);
try {
resolveField(response, name, value);
} catch (error) {
busboyStream.destroy(error);
}
}
});
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
if (encoding.toLowerCase() === 'base64') {
throw new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
busboyStream.destroy(
new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
),
);
return;
}
pendingFiles++;
const file = resolveFileInfo(response, name, filename, mimeType);
@@ -241,14 +248,18 @@ function decodeReplyFromBusboy<T>(
resolveFileChunk(response, file, chunk);
});
value.on('end', () => {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
try {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
}
queuedFields.length = 0;
}
queuedFields.length = 0;
} catch (error) {
busboyStream.destroy(error);
}
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "react-test-renderer",
"version": "19.0.0",
"version": "19.0.1",
"description": "React package for snapshot testing.",
"main": "index.js",
"repository": {
@@ -23,7 +23,7 @@
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^19.0.0"
"react": "^19.0.1"
},
"files": [
"LICENSE",

View File

@@ -4,7 +4,7 @@
"keywords": [
"react"
],
"version": "19.0.0",
"version": "19.0.1",
"homepage": "https://react.dev/",
"bugs": "https://github.com/facebook/react/issues",
"license": "MIT",

View File

@@ -12,4 +12,4 @@
// TODO: This module is used both by the release scripts and to expose a version
// at runtime. We should instead inject the version number as part of the build
// process, and use the ReactVersions.js module as the single source of truth.
export default '19.0.0';
export default '19.0.1';

View File

@@ -124,25 +124,34 @@ async function main() {
throw new Error(`Unknown release channel ${argv.releaseChannel}`);
}
} else {
// Running locally, no concurrency. Move each channel's build artifacts into
// a temporary directory so that they don't conflict.
buildForChannel('stable', '', '');
const stableDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', stableDir);
processStable(stableDir);
buildForChannel('experimental', '', '');
const experimentalDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', experimentalDir);
processExperimental(experimentalDir);
const releaseChannel = argv.releaseChannel;
if (releaseChannel === 'stable') {
buildForChannel('stable', '', '');
processStable('./build');
} else if (releaseChannel === 'experimental') {
buildForChannel('experimental', '', '');
processExperimental('./build');
} else {
// Running locally, no concurrency. Move each channel's build artifacts into
// a temporary directory so that they don't conflict.
buildForChannel('stable', '', '');
const stableDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', stableDir);
processStable(stableDir);
buildForChannel('experimental', '', '');
const experimentalDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', experimentalDir);
processExperimental(experimentalDir);
// Then merge the experimental folder into the stable one. processExperimental
// will have already removed conflicting files.
//
// In CI, merging is handled by the GitHub Download Artifacts plugin.
mergeDirsSync(experimentalDir + '/', stableDir + '/');
// Then merge the experimental folder into the stable one. processExperimental
// will have already removed conflicting files.
//
// In CI, merging is handled by the GitHub Download Artifacts plugin.
mergeDirsSync(experimentalDir + '/', stableDir + '/');
// Now restore the combined directory back to its original name
crossDeviceRenameSync(stableDir, './build');
// Now restore the combined directory back to its original name
crossDeviceRenameSync(stableDir, './build');
}
}
}