@samitouri / QOS-React / commits / 6a4c8f51fa

[DevTools] Store Webpack stats when building extensions (#34514)

Sebastian "Sebbie" Silbermann committed Sep 17, 2025 at 15:03 UTC 6a4c8f51fab9a2067dc91662af74041fe394f944
7 files changed +71 -11
.github/workflows/devtools_regression_tests.yml
+1 -1
@@ -92,7 +92,7 @@ jobs:
92 uses: actions/upload-artifact@v4
93 with:
94 name: react-devtools
95 - path: build/devtools.tgz
95 + path: build/devtools
96 if-no-files-found: error
97 # Simplifies getting the extension for local testing
98 - name: Archive chrome extension
.github/workflows/runtime_build_and_test.yml
+6 -1
@@ -766,6 +766,11 @@ jobs:
766 name: react-devtools-${{ matrix.browser }}-extension
767 path: build/devtools/${{ matrix.browser }}-extension.zip
768 if-no-files-found: error
769 + - name: Archive ${{ matrix.browser }} metadata
770 + uses: actions/upload-artifact@v4
771 + with:
772 + name: react-devtools-${{ matrix.browser }}-metadata
773 + path: build/devtools/webpack-stats.*.json
774
775 merge_devtools_artifacts:
776 name: Merge DevTools artifacts
@@ -776,7 +781,7 @@ jobs:
781 uses: actions/upload-artifact/merge@v4
782 with:
783 name: react-devtools
779 - pattern: react-devtools-*-extension
784 + pattern: react-devtools-*
785
786 run_devtools_e2e_tests:
787 name: Run DevTools e2e tests
packages/react-devtools-extensions/build.js
+36 -5
@@ -6,7 +6,7 @@ const archiver = require('archiver');
6 const {execSync} = require('child_process');
7 const {readFileSync, writeFileSync, createWriteStream} = require('fs');
8 const {copy, ensureDir, move, remove, pathExistsSync} = require('fs-extra');
9 -const {join, resolve} = require('path');
9 +const {join, resolve, basename} = require('path');
10 const {getGitCommit} = require('./utils');
11
12 // These files are copied along with Webpack-bundled files
@@ -80,8 +80,25 @@ const build = async (tempPath, manifestPath, envExtension = {}) => {
80
81 const copiedManifestPath = join(zipPath, 'manifest.json');
82
83 + let webpackStatsFilePath = null;
84 // Copy unbuilt source files to zip dir to be packaged:
84 - await copy(binPath, join(zipPath, 'build'));
85 + await copy(binPath, join(zipPath, 'build'), {
86 + filter: filePath => {
87 + if (basename(filePath).startsWith('webpack-stats.')) {
88 + webpackStatsFilePath = filePath;
89 + // The ZIP is the actual extension and doesn't need this metadata.
90 + return false;
91 + }
92 + return true;
93 + },
94 + });
95 + if (webpackStatsFilePath !== null) {
96 + await copy(
97 + webpackStatsFilePath,
98 + join(tempPath, basename(webpackStatsFilePath)),
99 + );
100 + webpackStatsFilePath = join(tempPath, basename(webpackStatsFilePath));
101 + }
102 await copy(manifestPath, copiedManifestPath);
103 await Promise.all(
104 STATIC_FILES.map(file => copy(join(__dirname, file), join(zipPath, file))),
@@ -120,9 +137,11 @@ const build = async (tempPath, manifestPath, envExtension = {}) => {
137 archive.finalize();
138 zipStream.on('close', () => resolvePromise());
139 });
140 +
141 + return webpackStatsFilePath;
142 };
143
125 -const postProcess = async (tempPath, destinationPath) => {
144 +const postProcess = async (tempPath, destinationPath, webpackStatsFilePath) => {
145 const unpackedSourcePath = join(tempPath, 'zip');
146 const packedSourcePath = join(tempPath, 'ReactDevTools.zip');
147 const packedDestPath = join(destinationPath, 'ReactDevTools.zip');
@@ -130,6 +149,14 @@ const postProcess = async (tempPath, destinationPath) => {
149
150 await move(unpackedSourcePath, unpackedDestPath); // Copy built files to destination
151 await move(packedSourcePath, packedDestPath); // Copy built files to destination
152 + if (webpackStatsFilePath !== null) {
153 + await move(
154 + webpackStatsFilePath,
155 + join(destinationPath, basename(webpackStatsFilePath)),
156 + );
157 + } else {
158 + console.log('No webpack-stats.json file was generated.');
159 + }
160 await remove(tempPath); // Clean up temp directory and files
161 };
162
@@ -158,10 +185,14 @@ const main = async buildId => {
185 const tempPath = join(__dirname, 'build', buildId);
186 await ensureLocalBuild();
187 await preProcess(destinationPath, tempPath);
161 - await build(tempPath, manifestPath, envExtension);
188 + const webpackStatsFilePath = await build(
189 + tempPath,
190 + manifestPath,
191 + envExtension,
192 + );
193
194 const builtUnpackedPath = join(destinationPath, 'unpacked');
164 - await postProcess(tempPath, destinationPath);
195 + await postProcess(tempPath, destinationPath, webpackStatsFilePath);
196
197 return builtUnpackedPath;
198 } catch (error) {
packages/react-devtools-extensions/package.json
+1
@@ -65,6 +65,7 @@
65 "webpack": "^5.82.1",
66 "webpack-cli": "^5.1.1",
67 "webpack-dev-server": "^4.15.0",
68 + "webpack-stats-plugin": "^1.1.3",
69 "workerize-loader": "^2.0.2"
70 },
71 "dependencies": {
packages/react-devtools-extensions/webpack.config.js
+20
@@ -6,6 +6,7 @@ const TerserPlugin = require('terser-webpack-plugin');
6 const {GITHUB_URL, getVersionString} = require('./utils');
7 const {resolveFeatureFlags} = require('react-devtools-shared/buildUtils');
8 const SourceMapIgnoreListPlugin = require('react-devtools-shared/SourceMapIgnoreListPlugin');
9 +const {StatsWriterPlugin} = require('webpack-stats-plugin');
10
11 const NODE_ENV = process.env.NODE_ENV;
12 if (!NODE_ENV) {
@@ -37,6 +38,21 @@ const IS_INTERNAL_MCP_BUILD = process.env.IS_INTERNAL_MCP_BUILD === 'true';
38
39 const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss';
40
41 +let statsFileName = `webpack-stats.${featureFlagTarget}.${__DEV__ ? 'development' : 'production'}`;
42 +if (IS_CHROME) {
43 + statsFileName += `.chrome`;
44 +}
45 +if (IS_FIREFOX) {
46 + statsFileName += `.firefox`;
47 +}
48 +if (IS_EDGE) {
49 + statsFileName += `.edge`;
50 +}
51 +if (IS_INTERNAL_MCP_BUILD) {
52 + statsFileName += `.mcp`;
53 +}
54 +statsFileName += '.json';
55 +
56 const babelOptions = {
57 configFile: resolve(
58 __dirname,
@@ -213,6 +229,10 @@ module.exports = {
229 );
230 },
231 },
232 + new StatsWriterPlugin({
233 + stats: 'verbose',
234 + filename: statsFileName,
235 + }),
236 ],
237 module: {
238 defaultRules: [
scripts/ci/pack_and_store_devtools_artifacts.sh
+2 -4
@@ -20,13 +20,11 @@ cd ../react-devtools-extensions
20 if [[ -n "$1" ]]; then
21 yarn build:$1
22 mv ./$1/build/ReactDevTools.zip ../../build/devtools/$1-extension.zip
23 + mv ./$1/build/webpack-stats.*.json ../../build/devtools/
24 else
25 yarn build
26 for browser in chrome firefox edge; do
27 mv ./$browser/build/ReactDevTools.zip ../../build/devtools/$browser-extension.zip
28 + mv ./$browser/build/webpack-stats.*.json ../../build/devtools/
29 done
30 fi
29 -
30 -# Compress all DevTools artifacts into a single tarball for easy download
31 -cd ../../build/devtools
32 -tar -zcvf ../devtools.tgz .
yarn.lock
+5
@@ -17792,6 +17792,11 @@ webpack-sources@^3.2.0, webpack-sources@^3.2.3:
17792 resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
17793 integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
17794
17795 +webpack-stats-plugin@^1.1.3:
17796 + version "1.1.3"
17797 + resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-1.1.3.tgz#ebcc36c8b468074ad737882e2043c1ce4b55d928"
17798 + integrity sha512-yUKYyy+e0iF/w31QdfioRKY+h3jDBRpthexBOWGKda99iu2l/wxYsI/XqdlP5IU58/0KB9CsJZgWNAl+/MPkRw==
17799 +
17800 webpack@^5.82.1:
17801 version "5.82.1"
17802 resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.1.tgz#8f38c78e53467556e8a89054ebd3ef6e9f67dbab"