Skip to content

Commit

Permalink
React version field should match package.json (#24445)
Browse files Browse the repository at this point in the history
The `version` field exported by the React package currently corresponds
to the `@next` release for that build. This updates the build script
to output the same version that is used in the package.json file.

It works by doing a find-and-replace of the React version after the
build has completed. This is a bit weird but it saves us from having
to build the `@next` and `@latest` releases separately; they are
identical except for the version numbers.
  • Loading branch information
acdlite committed Apr 26, 2022
1 parent 6bf3dee commit 22edb9f
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 21 deletions.
26 changes: 26 additions & 0 deletions packages/react/src/__tests__/ReactVersion-test.js
@@ -0,0 +1,26 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/

'use strict';

// NOTE: Intentionally using the dynamic version of the `gate` pragma to opt out
// the negative test behavior. If this test happens to pass when running
// against files source, that's fine. But all we care about is the behavior of
// the build artifacts.
// TODO: The experimental builds have a different version at runtime than
// the package.json because DevTools uses it for feature detection. Consider
// some other way of handling that.
test('ReactVersion matches package.json', () => {
if (gate(flags => flags.build && flags.stable && !flags.www)) {
const React = require('react');
const packageJSON = require('react/package.json');
expect(React.version).toBe(packageJSON.version);
}
});
85 changes: 64 additions & 21 deletions scripts/rollup/build-all-release-channels.js
Expand Up @@ -38,6 +38,20 @@ if (dateString.startsWith("'")) {
dateString = dateString.substr(1, 8);
}

// Build the artifacts using a placeholder React version. We'll then do a string
// replace to swap it with the correct version per release channel.
//
// The placeholder version is the same format that the "next" channel uses
const PLACEHOLDER_REACT_VERSION =
ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString;

// TODO: We should inject the React version using a build-time parameter
// instead of overwriting the source files.
fs.writeFileSync(
'./packages/shared/ReactVersion.js',
`export default '${PLACEHOLDER_REACT_VERSION}';\n`
);

if (process.env.CIRCLE_NODE_TOTAL) {
// In CI, we use multiple concurrent processes. Allocate half the processes to
// build the stable channel, and the other half for experimental. Override
Expand All @@ -48,33 +62,21 @@ if (process.env.CIRCLE_NODE_TOTAL) {
if (index < halfTotal) {
const nodeTotal = halfTotal;
const nodeIndex = index;
updateTheReactVersionThatDevToolsReads(
ReactVersion + '-' + sha + '-' + dateString
);
buildForChannel('stable', nodeTotal, nodeIndex);
processStable('./build');
} else {
const nodeTotal = total - halfTotal;
const nodeIndex = index - halfTotal;
updateTheReactVersionThatDevToolsReads(
ReactVersion + '-experimental-' + sha + '-' + dateString
);
buildForChannel('experimental', nodeTotal, nodeIndex);
processExperimental('./build');
}
} else {
// Running locally, no concurrency. Move each channel's build artifacts into
// a temporary directory so that they don't conflict.
updateTheReactVersionThatDevToolsReads(
ReactVersion + '-' + sha + '-' + dateString
);
buildForChannel('stable', '', '');
const stableDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', stableDir);
processStable(stableDir);
updateTheReactVersionThatDevToolsReads(
ReactVersion + '-experimental-' + sha + '-' + dateString
);
buildForChannel('experimental', '', '');
const experimentalDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', experimentalDir);
Expand Down Expand Up @@ -129,6 +131,10 @@ function processStable(buildDir) {
true
);
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-stable');
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-stable',
ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString
);

// Now do the semver ones
const semverVersionsMap = new Map();
Expand All @@ -142,6 +148,10 @@ function processStable(buildDir) {
defaultVersionIfNotFound,
false
);
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-stable-semver',
ReactVersion
);
}

if (fs.existsSync(buildDir + '/facebook-www')) {
Expand All @@ -152,6 +162,10 @@ function processStable(buildDir) {
fs.renameSync(filePath, filePath.replace('.js', '.classic.js'));
}
}
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-www',
ReactVersion + '-www-classic-' + sha + '-' + dateString
);
}

if (fs.existsSync(buildDir + '/sizes')) {
Expand All @@ -162,7 +176,7 @@ function processStable(buildDir) {
function processExperimental(buildDir, version) {
if (fs.existsSync(buildDir + '/node_modules')) {
const defaultVersionIfNotFound =
'0.0.0' + '-' + 'experimental' + '-' + sha + '-' + dateString;
'0.0.0' + '-experimental-' + sha + '-' + dateString;
const versionsMap = new Map();
for (const moduleName in stablePackages) {
versionsMap.set(moduleName, defaultVersionIfNotFound);
Expand All @@ -177,6 +191,13 @@ function processExperimental(buildDir, version) {
true
);
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-experimental');
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-experimental',
// TODO: The npm version for experimental releases does not include the
// React version, but the runtime version does so that DevTools can do
// feature detection. Decide what to do about this later.
ReactVersion + '-experimental-' + sha + '-' + dateString
);
}

if (fs.existsSync(buildDir + '/facebook-www')) {
Expand All @@ -187,6 +208,10 @@ function processExperimental(buildDir, version) {
fs.renameSync(filePath, filePath.replace('.js', '.modern.js'));
}
}
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-www',
ReactVersion + '-www-modern-' + sha + '-' + dateString
);
}

if (fs.existsSync(buildDir + '/sizes')) {
Expand Down Expand Up @@ -278,14 +303,32 @@ function updatePackageVersions(
}
}

function updateTheReactVersionThatDevToolsReads(version) {
// Overwrite the ReactVersion module before the build script runs so that it
// is included in the final bundles. This only runs in CI, so it's fine to
// edit the source file.
fs.writeFileSync(
'./packages/shared/ReactVersion.js',
`export default '${version}';\n`
);
function updatePlaceholderReactVersionInCompiledArtifacts(
artifactsDirectory,
newVersion
) {
// Update the version of React in the compiled artifacts by searching for
// the placeholder string and replacing it with a new one.
const artifactFilenames = String(
spawnSync('grep', [
'-lr',
PLACEHOLDER_REACT_VERSION,
'--',
artifactsDirectory,
]).stdout
)
.trim()
.split('\n')
.filter(filename => filename.endsWith('.js'));

for (const artifactFilename of artifactFilenames) {
const originalText = fs.readFileSync(artifactFilename, 'utf8');
const replacedText = originalText.replace(
PLACEHOLDER_REACT_VERSION,
newVersion
);
fs.writeFileSync(artifactFilename, replacedText);
}
}

/**
Expand Down

0 comments on commit 22edb9f

Please sign in to comment.