@samitouri / QOS-React-2 / commits / 22edb9f777

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 /**