Skip to content

Commit 22edb9f

Browse files
authored
React version field should match package.json (#24445)
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.
1 parent 6bf3dee commit 22edb9f

File tree

2 files changed

+90
-21
lines changed

2 files changed

+90
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its 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+
* @emails react-core
8+
* @jest-environment node
9+
*/
10+
11+
'use strict';
12+
13+
// NOTE: Intentionally using the dynamic version of the `gate` pragma to opt out
14+
// the negative test behavior. If this test happens to pass when running
15+
// against files source, that's fine. But all we care about is the behavior of
16+
// the build artifacts.
17+
// TODO: The experimental builds have a different version at runtime than
18+
// the package.json because DevTools uses it for feature detection. Consider
19+
// some other way of handling that.
20+
test('ReactVersion matches package.json', () => {
21+
if (gate(flags => flags.build && flags.stable && !flags.www)) {
22+
const React = require('react');
23+
const packageJSON = require('react/package.json');
24+
expect(React.version).toBe(packageJSON.version);
25+
}
26+
});

scripts/rollup/build-all-release-channels.js

+64-21
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ if (dateString.startsWith("'")) {
3838
dateString = dateString.substr(1, 8);
3939
}
4040

41+
// Build the artifacts using a placeholder React version. We'll then do a string
42+
// replace to swap it with the correct version per release channel.
43+
//
44+
// The placeholder version is the same format that the "next" channel uses
45+
const PLACEHOLDER_REACT_VERSION =
46+
ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString;
47+
48+
// TODO: We should inject the React version using a build-time parameter
49+
// instead of overwriting the source files.
50+
fs.writeFileSync(
51+
'./packages/shared/ReactVersion.js',
52+
`export default '${PLACEHOLDER_REACT_VERSION}';\n`
53+
);
54+
4155
if (process.env.CIRCLE_NODE_TOTAL) {
4256
// In CI, we use multiple concurrent processes. Allocate half the processes to
4357
// build the stable channel, and the other half for experimental. Override
@@ -48,33 +62,21 @@ if (process.env.CIRCLE_NODE_TOTAL) {
4862
if (index < halfTotal) {
4963
const nodeTotal = halfTotal;
5064
const nodeIndex = index;
51-
updateTheReactVersionThatDevToolsReads(
52-
ReactVersion + '-' + sha + '-' + dateString
53-
);
5465
buildForChannel('stable', nodeTotal, nodeIndex);
5566
processStable('./build');
5667
} else {
5768
const nodeTotal = total - halfTotal;
5869
const nodeIndex = index - halfTotal;
59-
updateTheReactVersionThatDevToolsReads(
60-
ReactVersion + '-experimental-' + sha + '-' + dateString
61-
);
6270
buildForChannel('experimental', nodeTotal, nodeIndex);
6371
processExperimental('./build');
6472
}
6573
} else {
6674
// Running locally, no concurrency. Move each channel's build artifacts into
6775
// a temporary directory so that they don't conflict.
68-
updateTheReactVersionThatDevToolsReads(
69-
ReactVersion + '-' + sha + '-' + dateString
70-
);
7176
buildForChannel('stable', '', '');
7277
const stableDir = tmp.dirSync().name;
7378
crossDeviceRenameSync('./build', stableDir);
7479
processStable(stableDir);
75-
updateTheReactVersionThatDevToolsReads(
76-
ReactVersion + '-experimental-' + sha + '-' + dateString
77-
);
7880
buildForChannel('experimental', '', '');
7981
const experimentalDir = tmp.dirSync().name;
8082
crossDeviceRenameSync('./build', experimentalDir);
@@ -129,6 +131,10 @@ function processStable(buildDir) {
129131
true
130132
);
131133
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-stable');
134+
updatePlaceholderReactVersionInCompiledArtifacts(
135+
buildDir + '/oss-stable',
136+
ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString
137+
);
132138

133139
// Now do the semver ones
134140
const semverVersionsMap = new Map();
@@ -142,6 +148,10 @@ function processStable(buildDir) {
142148
defaultVersionIfNotFound,
143149
false
144150
);
151+
updatePlaceholderReactVersionInCompiledArtifacts(
152+
buildDir + '/oss-stable-semver',
153+
ReactVersion
154+
);
145155
}
146156

147157
if (fs.existsSync(buildDir + '/facebook-www')) {
@@ -152,6 +162,10 @@ function processStable(buildDir) {
152162
fs.renameSync(filePath, filePath.replace('.js', '.classic.js'));
153163
}
154164
}
165+
updatePlaceholderReactVersionInCompiledArtifacts(
166+
buildDir + '/facebook-www',
167+
ReactVersion + '-www-classic-' + sha + '-' + dateString
168+
);
155169
}
156170

157171
if (fs.existsSync(buildDir + '/sizes')) {
@@ -162,7 +176,7 @@ function processStable(buildDir) {
162176
function processExperimental(buildDir, version) {
163177
if (fs.existsSync(buildDir + '/node_modules')) {
164178
const defaultVersionIfNotFound =
165-
'0.0.0' + '-' + 'experimental' + '-' + sha + '-' + dateString;
179+
'0.0.0' + '-experimental-' + sha + '-' + dateString;
166180
const versionsMap = new Map();
167181
for (const moduleName in stablePackages) {
168182
versionsMap.set(moduleName, defaultVersionIfNotFound);
@@ -177,6 +191,13 @@ function processExperimental(buildDir, version) {
177191
true
178192
);
179193
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-experimental');
194+
updatePlaceholderReactVersionInCompiledArtifacts(
195+
buildDir + '/oss-experimental',
196+
// TODO: The npm version for experimental releases does not include the
197+
// React version, but the runtime version does so that DevTools can do
198+
// feature detection. Decide what to do about this later.
199+
ReactVersion + '-experimental-' + sha + '-' + dateString
200+
);
180201
}
181202

182203
if (fs.existsSync(buildDir + '/facebook-www')) {
@@ -187,6 +208,10 @@ function processExperimental(buildDir, version) {
187208
fs.renameSync(filePath, filePath.replace('.js', '.modern.js'));
188209
}
189210
}
211+
updatePlaceholderReactVersionInCompiledArtifacts(
212+
buildDir + '/facebook-www',
213+
ReactVersion + '-www-modern-' + sha + '-' + dateString
214+
);
190215
}
191216

192217
if (fs.existsSync(buildDir + '/sizes')) {
@@ -278,14 +303,32 @@ function updatePackageVersions(
278303
}
279304
}
280305

281-
function updateTheReactVersionThatDevToolsReads(version) {
282-
// Overwrite the ReactVersion module before the build script runs so that it
283-
// is included in the final bundles. This only runs in CI, so it's fine to
284-
// edit the source file.
285-
fs.writeFileSync(
286-
'./packages/shared/ReactVersion.js',
287-
`export default '${version}';\n`
288-
);
306+
function updatePlaceholderReactVersionInCompiledArtifacts(
307+
artifactsDirectory,
308+
newVersion
309+
) {
310+
// Update the version of React in the compiled artifacts by searching for
311+
// the placeholder string and replacing it with a new one.
312+
const artifactFilenames = String(
313+
spawnSync('grep', [
314+
'-lr',
315+
PLACEHOLDER_REACT_VERSION,
316+
'--',
317+
artifactsDirectory,
318+
]).stdout
319+
)
320+
.trim()
321+
.split('\n')
322+
.filter(filename => filename.endsWith('.js'));
323+
324+
for (const artifactFilename of artifactFilenames) {
325+
const originalText = fs.readFileSync(artifactFilename, 'utf8');
326+
const replacedText = originalText.replace(
327+
PLACEHOLDER_REACT_VERSION,
328+
newVersion
329+
);
330+
fs.writeFileSync(artifactFilename, replacedText);
331+
}
289332
}
290333

291334
/**

0 commit comments

Comments
 (0)