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.
Andrew Clark committed
Apr 26, 2022 at 16:28 UTC
22edb9f777d27369fd2c1fad378f74e237b6dfd3
2 files changed
+90
-21
packages/react/src/__tests__/ReactVersion-test.js
new
+26
@@ -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
@@ -38,6 +38,20 @@ if (dateString.startsWith("'")) {
38
dateString = dateString.substr(1, 8);
39
}
40
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
+
55
if (process.env.CIRCLE_NODE_TOTAL) {
56
// In CI, we use multiple concurrent processes. Allocate half the processes to
57
// build the stable channel, and the other half for experimental. Override
@@ -48,33 +62,21 @@ if (process.env.CIRCLE_NODE_TOTAL) {
62
if (index < halfTotal) {
63
const nodeTotal = halfTotal;
64
const nodeIndex = index;
51
- updateTheReactVersionThatDevToolsReads(
52
- ReactVersion + '-' + sha + '-' + dateString
53
- );
65
buildForChannel('stable', nodeTotal, nodeIndex);
66
processStable('./build');
67
} else {
68
const nodeTotal = total - halfTotal;
69
const nodeIndex = index - halfTotal;
59
- updateTheReactVersionThatDevToolsReads(
60
- ReactVersion + '-experimental-' + sha + '-' + dateString
61
- );
70
buildForChannel('experimental', nodeTotal, nodeIndex);
71
processExperimental('./build');
72
}
73
} else {
74
// Running locally, no concurrency. Move each channel's build artifacts into
75
// a temporary directory so that they don't conflict.
68
- updateTheReactVersionThatDevToolsReads(
69
- ReactVersion + '-' + sha + '-' + dateString
70
- );
76
buildForChannel('stable', '', '');
77
const stableDir = tmp.dirSync().name;
78
crossDeviceRenameSync('./build', stableDir);
79
processStable(stableDir);
75
- updateTheReactVersionThatDevToolsReads(
76
- ReactVersion + '-experimental-' + sha + '-' + dateString
77
- );
80
buildForChannel('experimental', '', '');
81
const experimentalDir = tmp.dirSync().name;
82
crossDeviceRenameSync('./build', experimentalDir);
@@ -129,6 +131,10 @@ function processStable(buildDir) {
131
true
132
);
133
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-stable');
134
+ updatePlaceholderReactVersionInCompiledArtifacts(
135
+ buildDir + '/oss-stable',
136
+ ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString
137
+ );
138
139
// Now do the semver ones
140
const semverVersionsMap = new Map();
@@ -142,6 +148,10 @@ function processStable(buildDir) {
148
defaultVersionIfNotFound,
149
false
150
);
151
+ updatePlaceholderReactVersionInCompiledArtifacts(
152
+ buildDir + '/oss-stable-semver',
153
+ ReactVersion
154
+ );
155
}
156
157
if (fs.existsSync(buildDir + '/facebook-www')) {
@@ -152,6 +162,10 @@ function processStable(buildDir) {
162
fs.renameSync(filePath, filePath.replace('.js', '.classic.js'));
163
}
164
}
165
+ updatePlaceholderReactVersionInCompiledArtifacts(
166
+ buildDir + '/facebook-www',
167
+ ReactVersion + '-www-classic-' + sha + '-' + dateString
168
+ );
169
}
170
171
if (fs.existsSync(buildDir + '/sizes')) {
@@ -162,7 +176,7 @@ function processStable(buildDir) {
176
function processExperimental(buildDir, version) {
177
if (fs.existsSync(buildDir + '/node_modules')) {
178
const defaultVersionIfNotFound =
165
- '0.0.0' + '-' + 'experimental' + '-' + sha + '-' + dateString;
179
+ '0.0.0' + '-experimental-' + sha + '-' + dateString;
180
const versionsMap = new Map();
181
for (const moduleName in stablePackages) {
182
versionsMap.set(moduleName, defaultVersionIfNotFound);
@@ -177,6 +191,13 @@ function processExperimental(buildDir, version) {
191
true
192
);
193
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
+ );
201
}
202
203
if (fs.existsSync(buildDir + '/facebook-www')) {
@@ -187,6 +208,10 @@ function processExperimental(buildDir, version) {
208
fs.renameSync(filePath, filePath.replace('.js', '.modern.js'));
209
}
210
}
211
+ updatePlaceholderReactVersionInCompiledArtifacts(
212
+ buildDir + '/facebook-www',
213
+ ReactVersion + '-www-modern-' + sha + '-' + dateString
214
+ );
215
}
216
217
if (fs.existsSync(buildDir + '/sizes')) {
@@ -278,14 +303,32 @@ function updatePackageVersions(
303
}
304
}
305
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
+ }
332
}
333
334
/**