Repository: facebook/react
Pull request: Use content hash for facebook-www builds

Currently, any commit to React causes an internal sync since the Git commit hash is part of the build. This creates a lot more sync commits and noise than necessary, see: https://github.com/facebook/react/commits/builds/facebook-www

This PR changes the version string to be a hash of the target build files instead. This way we get a new version with any change that actually impacts the generated files and still have a matching version across the files.

A downside is that the internal React upgrade command can no longer print a changelog, but I don't think folks have been using that since using diff train. (If this PR is approved, I'll update the internal command to no longer depend on the `REVISIONS` file).

Changed files:
filename: .github/workflows/commit_artifacts.yml
patch: @@ -123,10 +123,9 @@ jobs:
             ./compiled/babel-plugin-react-refresh/index.js
 
           ls -R ./compiled
-      - name: Add REVISION files
+      - name: Add REVISION file
         run: |
           echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
-          cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
       - uses: actions/upload-artifact@v3
         with:
           name: compiled
@@ -146,7 +145,16 @@ jobs:
           name: compiled
           path: compiled/
       - run: git status -u
+      - name: Check if only the REVISION file has changed
+        id: check_should_commit
+        run: |
+          if git status --porcelain | grep -qv '/REVISION$'; then
+            echo "should_commit=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "should_commit=false" >> "$GITHUB_OUTPUT"
+          fi
       - name: Commit changes to branch
+        if: steps.check_should_commit.outputs.should_commit == 'true'
         uses: stefanzweifel/git-auto-commit-action@v4
         with:
           commit_message: |

filename: scripts/rollup/build-all-release-channels.js
patch: @@ -2,6 +2,7 @@
 
 /* eslint-disable no-for-of-loops/no-for-of-loops */
 
+const crypto = require('node:crypto');
 const fs = require('fs');
 const fse = require('fs-extra');
 const {spawnSync} = require('child_process');
@@ -40,10 +41,7 @@ if (dateString.startsWith("'")) {
 
 // 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;
+const PLACEHOLDER_REACT_VERSION = ReactVersion + '-PLACEHOLDER';
 
 // TODO: We should inject the React version using a build-time parameter
 // instead of overwriting the source files.
@@ -164,19 +162,27 @@ function processStable(buildDir) {
   }
 
   if (fs.existsSync(buildDir + '/facebook-www')) {
-    for (const fileName of fs.readdirSync(buildDir + '/facebook-www')) {
+    const hash = crypto.createHash('sha1');
+    for (const fileName of fs.readdirSync(buildDir + '/facebook-www').sort()) {
       const filePath = buildDir + '/facebook-www/' + fileName;
       const stats = fs.statSync(filePath);
       if (!stats.isDirectory()) {
+        hash.update(fs.readFileSync(filePath));
         fs.renameSync(filePath, filePath.replace('.js', '.classic.js'));
       }
     }
     updatePlaceholderReactVersionInCompiledArtifacts(
       buildDir + '/facebook-www',
-      ReactVersion + '-www-classic-' + sha + '-' + dateString
+      ReactVersion + '-www-classic-' + hash.digest('hex').substr(0, 8)
     );
   }
 
+  // Update remaining placeholders with next channel version
+  updatePlaceholderReactVersionInCompiledArtifacts(
+    buildDir,
+    ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString
+  );
+
   if (fs.existsSync(buildDir + '/sizes')) {
     fs.renameSync(buildDir + '/sizes', buildDir + '/sizes-stable');
   }
@@ -210,19 +216,27 @@ function processExperimental(buildDir, version) {
   }
 
   if (fs.existsSync(buildDir + '/facebook-www')) {
-    for (const fileName of fs.readdirSync(buildDir + '/facebook-www')) {
+    const hash = crypto.createHash('sha1');
+    for (const fileName of fs.readdirSync(buildDir + '/facebook-www').sort()) {
       const filePath = buildDir + '/facebook-www/' + fileName;
       const stats = fs.statSync(filePath);
       if (!stats.isDirectory()) {
+        hash.update(fs.readFileSync(filePath));
         fs.renameSync(filePath, filePath.replace('.js', '.modern.js'));
       }
     }
     updatePlaceholderReactVersionInCompiledArtifacts(
       buildDir + '/facebook-www',
-      ReactVersion + '-www-modern-' + sha + '-' + dateString
+      ReactVersion + '-www-modern-' + hash.digest('hex').substr(0, 8)
     );
   }
 
+  // Update remaining placeholders with next channel version
+  updatePlaceholderReactVersionInCompiledArtifacts(
+    buildDir,
+    ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString
+  );
+
   if (fs.existsSync(buildDir + '/sizes')) {
     fs.renameSync(buildDir + '/sizes', buildDir + '/sizes-experimental');
   }