From 16df13b84c40d92ca1bb2dee552f5a780e292d41 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 17 Sep 2025 14:52:32 +0200 Subject: [PATCH 1/7] [DevTools] Minify backend (#34507) --- .../webpack.backend.js | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/react-devtools-extensions/webpack.backend.js b/packages/react-devtools-extensions/webpack.backend.js index 4bfa05183067e..18b86fd3d3733 100644 --- a/packages/react-devtools-extensions/webpack.backend.js +++ b/packages/react-devtools-extensions/webpack.backend.js @@ -2,6 +2,7 @@ const {resolve, isAbsolute, relative} = require('path'); const Webpack = require('webpack'); +const TerserPlugin = require('terser-webpack-plugin'); const {resolveFeatureFlags} = require('react-devtools-shared/buildUtils'); const SourceMapIgnoreListPlugin = require('react-devtools-shared/SourceMapIgnoreListPlugin'); @@ -56,16 +57,32 @@ module.exports = { }, }, optimization: { - minimize: false, + minimize: !__DEV__, + minimizer: [ + new TerserPlugin({ + terserOptions: { + compress: { + unused: true, + dead_code: true, + }, + mangle: { + keep_fnames: true, + }, + format: { + comments: false, + }, + }, + extractComments: false, + }), + ], }, plugins: [ new Webpack.ProvidePlugin({ process: 'process/browser', }), new Webpack.DefinePlugin({ - __DEV__: true, + __DEV__, __PROFILE__: false, - __DEV____DEV__: true, // By importing `shared/` we may import ReactFeatureFlags __EXPERIMENTAL__: true, 'process.env.DEVTOOLS_PACKAGE': `"react-devtools-extensions"`, From 6a4c8f51fab9a2067dc91662af74041fe394f944 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 17 Sep 2025 15:03:12 +0200 Subject: [PATCH 2/7] [DevTools] Store Webpack stats when building extensions (#34514) --- .../workflows/devtools_regression_tests.yml | 2 +- .github/workflows/runtime_build_and_test.yml | 7 +++- packages/react-devtools-extensions/build.js | 41 ++++++++++++++++--- .../react-devtools-extensions/package.json | 1 + .../webpack.config.js | 20 +++++++++ .../ci/pack_and_store_devtools_artifacts.sh | 6 +-- yarn.lock | 5 +++ 7 files changed, 71 insertions(+), 11 deletions(-) diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml index 0b70cfaf4e9ff..13b37f1743b45 100644 --- a/.github/workflows/devtools_regression_tests.yml +++ b/.github/workflows/devtools_regression_tests.yml @@ -92,7 +92,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: react-devtools - path: build/devtools.tgz + path: build/devtools if-no-files-found: error # Simplifies getting the extension for local testing - name: Archive chrome extension diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index d9fb47da3b204..8ecb956e6b9c6 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -766,6 +766,11 @@ jobs: name: react-devtools-${{ matrix.browser }}-extension path: build/devtools/${{ matrix.browser }}-extension.zip if-no-files-found: error + - name: Archive ${{ matrix.browser }} metadata + uses: actions/upload-artifact@v4 + with: + name: react-devtools-${{ matrix.browser }}-metadata + path: build/devtools/webpack-stats.*.json merge_devtools_artifacts: name: Merge DevTools artifacts @@ -776,7 +781,7 @@ jobs: uses: actions/upload-artifact/merge@v4 with: name: react-devtools - pattern: react-devtools-*-extension + pattern: react-devtools-* run_devtools_e2e_tests: name: Run DevTools e2e tests diff --git a/packages/react-devtools-extensions/build.js b/packages/react-devtools-extensions/build.js index 4d95af2a0a1aa..43bb7472be251 100644 --- a/packages/react-devtools-extensions/build.js +++ b/packages/react-devtools-extensions/build.js @@ -6,7 +6,7 @@ const archiver = require('archiver'); const {execSync} = require('child_process'); const {readFileSync, writeFileSync, createWriteStream} = require('fs'); const {copy, ensureDir, move, remove, pathExistsSync} = require('fs-extra'); -const {join, resolve} = require('path'); +const {join, resolve, basename} = require('path'); const {getGitCommit} = require('./utils'); // These files are copied along with Webpack-bundled files @@ -80,8 +80,25 @@ const build = async (tempPath, manifestPath, envExtension = {}) => { const copiedManifestPath = join(zipPath, 'manifest.json'); + let webpackStatsFilePath = null; // Copy unbuilt source files to zip dir to be packaged: - await copy(binPath, join(zipPath, 'build')); + await copy(binPath, join(zipPath, 'build'), { + filter: filePath => { + if (basename(filePath).startsWith('webpack-stats.')) { + webpackStatsFilePath = filePath; + // The ZIP is the actual extension and doesn't need this metadata. + return false; + } + return true; + }, + }); + if (webpackStatsFilePath !== null) { + await copy( + webpackStatsFilePath, + join(tempPath, basename(webpackStatsFilePath)), + ); + webpackStatsFilePath = join(tempPath, basename(webpackStatsFilePath)); + } await copy(manifestPath, copiedManifestPath); await Promise.all( STATIC_FILES.map(file => copy(join(__dirname, file), join(zipPath, file))), @@ -120,9 +137,11 @@ const build = async (tempPath, manifestPath, envExtension = {}) => { archive.finalize(); zipStream.on('close', () => resolvePromise()); }); + + return webpackStatsFilePath; }; -const postProcess = async (tempPath, destinationPath) => { +const postProcess = async (tempPath, destinationPath, webpackStatsFilePath) => { const unpackedSourcePath = join(tempPath, 'zip'); const packedSourcePath = join(tempPath, 'ReactDevTools.zip'); const packedDestPath = join(destinationPath, 'ReactDevTools.zip'); @@ -130,6 +149,14 @@ const postProcess = async (tempPath, destinationPath) => { await move(unpackedSourcePath, unpackedDestPath); // Copy built files to destination await move(packedSourcePath, packedDestPath); // Copy built files to destination + if (webpackStatsFilePath !== null) { + await move( + webpackStatsFilePath, + join(destinationPath, basename(webpackStatsFilePath)), + ); + } else { + console.log('No webpack-stats.json file was generated.'); + } await remove(tempPath); // Clean up temp directory and files }; @@ -158,10 +185,14 @@ const main = async buildId => { const tempPath = join(__dirname, 'build', buildId); await ensureLocalBuild(); await preProcess(destinationPath, tempPath); - await build(tempPath, manifestPath, envExtension); + const webpackStatsFilePath = await build( + tempPath, + manifestPath, + envExtension, + ); const builtUnpackedPath = join(destinationPath, 'unpacked'); - await postProcess(tempPath, destinationPath); + await postProcess(tempPath, destinationPath, webpackStatsFilePath); return builtUnpackedPath; } catch (error) { diff --git a/packages/react-devtools-extensions/package.json b/packages/react-devtools-extensions/package.json index c48e4d3b195bb..383f7d4211894 100644 --- a/packages/react-devtools-extensions/package.json +++ b/packages/react-devtools-extensions/package.json @@ -65,6 +65,7 @@ "webpack": "^5.82.1", "webpack-cli": "^5.1.1", "webpack-dev-server": "^4.15.0", + "webpack-stats-plugin": "^1.1.3", "workerize-loader": "^2.0.2" }, "dependencies": { diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js index 4a3052517c851..13a37ce25e2af 100644 --- a/packages/react-devtools-extensions/webpack.config.js +++ b/packages/react-devtools-extensions/webpack.config.js @@ -6,6 +6,7 @@ const TerserPlugin = require('terser-webpack-plugin'); const {GITHUB_URL, getVersionString} = require('./utils'); const {resolveFeatureFlags} = require('react-devtools-shared/buildUtils'); const SourceMapIgnoreListPlugin = require('react-devtools-shared/SourceMapIgnoreListPlugin'); +const {StatsWriterPlugin} = require('webpack-stats-plugin'); const NODE_ENV = process.env.NODE_ENV; if (!NODE_ENV) { @@ -37,6 +38,21 @@ const IS_INTERNAL_MCP_BUILD = process.env.IS_INTERNAL_MCP_BUILD === 'true'; const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss'; +let statsFileName = `webpack-stats.${featureFlagTarget}.${__DEV__ ? 'development' : 'production'}`; +if (IS_CHROME) { + statsFileName += `.chrome`; +} +if (IS_FIREFOX) { + statsFileName += `.firefox`; +} +if (IS_EDGE) { + statsFileName += `.edge`; +} +if (IS_INTERNAL_MCP_BUILD) { + statsFileName += `.mcp`; +} +statsFileName += '.json'; + const babelOptions = { configFile: resolve( __dirname, @@ -213,6 +229,10 @@ module.exports = { ); }, }, + new StatsWriterPlugin({ + stats: 'verbose', + filename: statsFileName, + }), ], module: { defaultRules: [ diff --git a/scripts/ci/pack_and_store_devtools_artifacts.sh b/scripts/ci/pack_and_store_devtools_artifacts.sh index 5118b42624732..d427a303e5adf 100755 --- a/scripts/ci/pack_and_store_devtools_artifacts.sh +++ b/scripts/ci/pack_and_store_devtools_artifacts.sh @@ -20,13 +20,11 @@ cd ../react-devtools-extensions if [[ -n "$1" ]]; then yarn build:$1 mv ./$1/build/ReactDevTools.zip ../../build/devtools/$1-extension.zip + mv ./$1/build/webpack-stats.*.json ../../build/devtools/ else yarn build for browser in chrome firefox edge; do mv ./$browser/build/ReactDevTools.zip ../../build/devtools/$browser-extension.zip + mv ./$browser/build/webpack-stats.*.json ../../build/devtools/ done fi - -# Compress all DevTools artifacts into a single tarball for easy download -cd ../../build/devtools -tar -zcvf ../devtools.tgz . diff --git a/yarn.lock b/yarn.lock index f58b4979fa33d..84de7c378267f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17792,6 +17792,11 @@ webpack-sources@^3.2.0, webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== +webpack-stats-plugin@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-1.1.3.tgz#ebcc36c8b468074ad737882e2043c1ce4b55d928" + integrity sha512-yUKYyy+e0iF/w31QdfioRKY+h3jDBRpthexBOWGKda99iu2l/wxYsI/XqdlP5IU58/0KB9CsJZgWNAl+/MPkRw== + webpack@^5.82.1: version "5.82.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.1.tgz#8f38c78e53467556e8a89054ebd3ef6e9f67dbab" From 81d66927afd396f446fbacc3dbae2b2e0033f852 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 17 Sep 2025 15:36:21 +0200 Subject: [PATCH 3/7] [DevTools] Stop polyfilling `Buffer` (#34512) --- .../webpack.config.js | 1 - .../webpack.config.frontend.js | 1 - .../react-devtools-inline/webpack.config.js | 1 - packages/react-devtools-shared/package.json | 6 +- .../parseHookNames/loadSourceAndMetadata.js | 12 +- scripts/ci/run_devtools_e2e_tests.js | 6 +- yarn.lock | 267 ++++++------------ 7 files changed, 90 insertions(+), 204 deletions(-) diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js index 13a37ce25e2af..26580dd853969 100644 --- a/packages/react-devtools-extensions/webpack.config.js +++ b/packages/react-devtools-extensions/webpack.config.js @@ -119,7 +119,6 @@ module.exports = { plugins: [ new Webpack.ProvidePlugin({ process: 'process/browser', - Buffer: ['buffer', 'Buffer'], }), new Webpack.DefinePlugin({ __DEV__, diff --git a/packages/react-devtools-fusebox/webpack.config.frontend.js b/packages/react-devtools-fusebox/webpack.config.frontend.js index ea04f4dad2d0d..1266d3d82895e 100644 --- a/packages/react-devtools-fusebox/webpack.config.frontend.js +++ b/packages/react-devtools-fusebox/webpack.config.frontend.js @@ -74,7 +74,6 @@ module.exports = { new MiniCssExtractPlugin(), new Webpack.ProvidePlugin({ process: 'process/browser', - Buffer: ['buffer', 'Buffer'], }), new Webpack.DefinePlugin({ __DEV__, diff --git a/packages/react-devtools-inline/webpack.config.js b/packages/react-devtools-inline/webpack.config.js index 9fa900dfa65f2..358a18f0e7d49 100644 --- a/packages/react-devtools-inline/webpack.config.js +++ b/packages/react-devtools-inline/webpack.config.js @@ -65,7 +65,6 @@ module.exports = { plugins: [ new Webpack.ProvidePlugin({ process: 'process/browser', - Buffer: ['buffer', 'Buffer'], }), new Webpack.DefinePlugin({ __DEV__, diff --git a/packages/react-devtools-shared/package.json b/packages/react-devtools-shared/package.json index a8daa42a0d0c2..543ac37e97614 100644 --- a/packages/react-devtools-shared/package.json +++ b/packages/react-devtools-shared/package.json @@ -10,11 +10,11 @@ "react-dom-15": "npm:react-dom@^15" }, "dependencies": { - "@babel/parser": "^7.12.5", - "@babel/preset-env": "^7.11.0", + "@babel/parser": "^7.28.3", + "@babel/preset-env": "7.26.9", "@babel/preset-flow": "^7.10.4", "@babel/runtime": "^7.11.2", - "@babel/traverse": "^7.12.5", + "@babel/traverse": "^7.28.3", "@reach/menu-button": "^0.16.1", "@reach/tooltip": "^0.16.0", "clipboard-js": "^0.3.6", diff --git a/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js index 5001ff31bf252..6d3f1223f6f6c 100644 --- a/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js +++ b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js @@ -119,17 +119,7 @@ export async function loadSourceAndMetadata( } function decodeBase64String(encoded: string): Object { - if (typeof atob === 'function') { - return atob(encoded); - } else if ( - typeof Buffer !== 'undefined' && - Buffer !== null && - typeof Buffer.from === 'function' - ) { - return Buffer.from(encoded, 'base64'); - } else { - throw Error('Cannot decode base64 string'); - } + return atob(encoded); } function extractAndLoadSourceMapJSON( diff --git a/scripts/ci/run_devtools_e2e_tests.js b/scripts/ci/run_devtools_e2e_tests.js index ca28ba85b2076..0d54e710b4d87 100755 --- a/scripts/ci/run_devtools_e2e_tests.js +++ b/scripts/ci/run_devtools_e2e_tests.js @@ -55,7 +55,11 @@ function buildInlinePackage() { logDim(data); }); buildProcess.stderr.on('data', data => { - if (`${data}`.includes('Warning')) { + if ( + `${data}`.includes('Warning') || + // E.g. [BABEL] Note: The code generator has deoptimised the styling of * as it exceeds the max of 500KB. + `${data}`.includes('[BABEL] Note') + ) { logDim(data); } else { logError(`Error:\n${data}`); diff --git a/yarn.lock b/yarn.lock index 84de7c378267f..f77d4483c8737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -165,15 +165,6 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.12.5": - version "7.12.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de" - integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A== - dependencies: - "@babel/types" "^7.12.5" - jsesc "^2.5.1" - source-map "^0.5.0" - "@babel/generator@^7.20.7", "@babel/generator@^7.24.5", "@babel/generator@^7.7.2": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" @@ -927,11 +918,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.3.tgz#9b530eecb071fd0c93519df25c5ff9f14759f298" integrity sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ== -"@babel/parser@^7.12.5": - version "7.12.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0" - integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ== - "@babel/parser@^7.14.8": version "7.14.8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4" @@ -2219,81 +2205,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.25.9" "@babel/helper-plugin-utils" "^7.25.9" -"@babel/preset-env@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.11.0.tgz#860ee38f2ce17ad60480c2021ba9689393efb796" - integrity sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg== - dependencies: - "@babel/compat-data" "^7.11.0" - "@babel/helper-compilation-targets" "^7.10.4" - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-proposal-async-generator-functions" "^7.10.4" - "@babel/plugin-proposal-class-properties" "^7.10.4" - "@babel/plugin-proposal-dynamic-import" "^7.10.4" - "@babel/plugin-proposal-export-namespace-from" "^7.10.4" - "@babel/plugin-proposal-json-strings" "^7.10.4" - "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" - "@babel/plugin-proposal-numeric-separator" "^7.10.4" - "@babel/plugin-proposal-object-rest-spread" "^7.11.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" - "@babel/plugin-proposal-optional-chaining" "^7.11.0" - "@babel/plugin-proposal-private-methods" "^7.10.4" - "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.10.4" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.10.4" - "@babel/plugin-transform-arrow-functions" "^7.10.4" - "@babel/plugin-transform-async-to-generator" "^7.10.4" - "@babel/plugin-transform-block-scoped-functions" "^7.10.4" - "@babel/plugin-transform-block-scoping" "^7.10.4" - "@babel/plugin-transform-classes" "^7.10.4" - "@babel/plugin-transform-computed-properties" "^7.10.4" - "@babel/plugin-transform-destructuring" "^7.10.4" - "@babel/plugin-transform-dotall-regex" "^7.10.4" - "@babel/plugin-transform-duplicate-keys" "^7.10.4" - "@babel/plugin-transform-exponentiation-operator" "^7.10.4" - "@babel/plugin-transform-for-of" "^7.10.4" - "@babel/plugin-transform-function-name" "^7.10.4" - "@babel/plugin-transform-literals" "^7.10.4" - "@babel/plugin-transform-member-expression-literals" "^7.10.4" - "@babel/plugin-transform-modules-amd" "^7.10.4" - "@babel/plugin-transform-modules-commonjs" "^7.10.4" - "@babel/plugin-transform-modules-systemjs" "^7.10.4" - "@babel/plugin-transform-modules-umd" "^7.10.4" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" - "@babel/plugin-transform-new-target" "^7.10.4" - "@babel/plugin-transform-object-super" "^7.10.4" - "@babel/plugin-transform-parameters" "^7.10.4" - "@babel/plugin-transform-property-literals" "^7.10.4" - "@babel/plugin-transform-regenerator" "^7.10.4" - "@babel/plugin-transform-reserved-words" "^7.10.4" - "@babel/plugin-transform-shorthand-properties" "^7.10.4" - "@babel/plugin-transform-spread" "^7.11.0" - "@babel/plugin-transform-sticky-regex" "^7.10.4" - "@babel/plugin-transform-template-literals" "^7.10.4" - "@babel/plugin-transform-typeof-symbol" "^7.10.4" - "@babel/plugin-transform-unicode-escapes" "^7.10.4" - "@babel/plugin-transform-unicode-regex" "^7.10.4" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.11.0" - browserslist "^4.12.0" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/preset-env@^7.26.9": +"@babel/preset-env@7.26.9", "@babel/preset-env@^7.26.9": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.9.tgz#2ec64e903d0efe743699f77a10bdf7955c2123c3" integrity sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ== @@ -2368,6 +2280,80 @@ core-js-compat "^3.40.0" semver "^6.3.1" +"@babel/preset-env@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.11.0.tgz#860ee38f2ce17ad60480c2021ba9689393efb796" + integrity sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg== + dependencies: + "@babel/compat-data" "^7.11.0" + "@babel/helper-compilation-targets" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-async-generator-functions" "^7.10.4" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-dynamic-import" "^7.10.4" + "@babel/plugin-proposal-export-namespace-from" "^7.10.4" + "@babel/plugin-proposal-json-strings" "^7.10.4" + "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.11.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.11.0" + "@babel/plugin-proposal-private-methods" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.10.4" + "@babel/plugin-transform-arrow-functions" "^7.10.4" + "@babel/plugin-transform-async-to-generator" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions" "^7.10.4" + "@babel/plugin-transform-block-scoping" "^7.10.4" + "@babel/plugin-transform-classes" "^7.10.4" + "@babel/plugin-transform-computed-properties" "^7.10.4" + "@babel/plugin-transform-destructuring" "^7.10.4" + "@babel/plugin-transform-dotall-regex" "^7.10.4" + "@babel/plugin-transform-duplicate-keys" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator" "^7.10.4" + "@babel/plugin-transform-for-of" "^7.10.4" + "@babel/plugin-transform-function-name" "^7.10.4" + "@babel/plugin-transform-literals" "^7.10.4" + "@babel/plugin-transform-member-expression-literals" "^7.10.4" + "@babel/plugin-transform-modules-amd" "^7.10.4" + "@babel/plugin-transform-modules-commonjs" "^7.10.4" + "@babel/plugin-transform-modules-systemjs" "^7.10.4" + "@babel/plugin-transform-modules-umd" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" + "@babel/plugin-transform-new-target" "^7.10.4" + "@babel/plugin-transform-object-super" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-property-literals" "^7.10.4" + "@babel/plugin-transform-regenerator" "^7.10.4" + "@babel/plugin-transform-reserved-words" "^7.10.4" + "@babel/plugin-transform-shorthand-properties" "^7.10.4" + "@babel/plugin-transform-spread" "^7.11.0" + "@babel/plugin-transform-sticky-regex" "^7.10.4" + "@babel/plugin-transform-template-literals" "^7.10.4" + "@babel/plugin-transform-typeof-symbol" "^7.10.4" + "@babel/plugin-transform-unicode-escapes" "^7.10.4" + "@babel/plugin-transform-unicode-regex" "^7.10.4" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.11.0" + browserslist "^4.12.0" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + "@babel/preset-flow@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.10.4.tgz#e0d9c72f8cb02d1633f6a5b7b16763aa2edf659f" @@ -2539,21 +2525,6 @@ globals "^11.1.0" lodash "^4.17.19" -"@babel/traverse@^7.12.5": - version "7.12.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.5.tgz#78a0c68c8e8a35e4cacfd31db8bb303d5606f095" - integrity sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.5" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.12.5" - "@babel/types" "^7.12.5" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.19" - "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.7", "@babel/traverse@^7.24.5", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a" @@ -2593,7 +2564,7 @@ "@babel/types" "^7.28.2" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.13", "@babel/types@^7.12.5", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3": +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.13", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce" integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw== @@ -8298,7 +8269,7 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" -"eslint-v7@npm:eslint@^7.7.0": +"eslint-v7@npm:eslint@^7.7.0", eslint@^7.7.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -8497,52 +8468,6 @@ eslint@8.57.0: strip-ansi "^6.0.1" text-table "^0.2.0" -eslint@^7.7.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - espree@10.0.1, espree@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" @@ -14344,7 +14269,7 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -"prettier-2@npm:prettier@^2": +"prettier-2@npm:prettier@^2", prettier@^2.5.1: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -14359,11 +14284,6 @@ prettier@^1.19.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -prettier@^2.5.1: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== - pretty-format@^29.4.1: version "29.4.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.1.tgz#0da99b532559097b8254298da7c75a0785b1751c" @@ -16238,7 +16158,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16273,15 +16193,6 @@ string-width@^4.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -16342,7 +16253,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16370,13 +16281,6 @@ strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -17985,7 +17889,7 @@ workerize-loader@^2.0.2: dependencies: loader-utils "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18003,15 +17907,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 27b4076ab051b4d0e1ae6cec3266367294df024c Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 17 Sep 2025 15:45:25 +0200 Subject: [PATCH 4/7] [DevTools] Use a single Webpack config for the extensions (#34513) --- packages/react-devtools-extensions/build.js | 8 -- .../webpack.backend.js | 135 ------------------ .../webpack.config.js | 13 +- 3 files changed, 11 insertions(+), 145 deletions(-) delete mode 100644 packages/react-devtools-extensions/webpack.backend.js diff --git a/packages/react-devtools-extensions/build.js b/packages/react-devtools-extensions/build.js index 43bb7472be251..1c8322cd5c98e 100644 --- a/packages/react-devtools-extensions/build.js +++ b/packages/react-devtools-extensions/build.js @@ -66,14 +66,6 @@ const build = async (tempPath, manifestPath, envExtension = {}) => { stdio: 'inherit', }, ); - execSync( - `${webpackPath} --config webpack.backend.js --output-path ${binPath}`, - { - cwd: __dirname, - env: mergedEnv, - stdio: 'inherit', - }, - ); // Make temp dir await ensureDir(zipPath); diff --git a/packages/react-devtools-extensions/webpack.backend.js b/packages/react-devtools-extensions/webpack.backend.js deleted file mode 100644 index 18b86fd3d3733..0000000000000 --- a/packages/react-devtools-extensions/webpack.backend.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -const {resolve, isAbsolute, relative} = require('path'); -const Webpack = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); - -const {resolveFeatureFlags} = require('react-devtools-shared/buildUtils'); -const SourceMapIgnoreListPlugin = require('react-devtools-shared/SourceMapIgnoreListPlugin'); - -const {GITHUB_URL, getVersionString} = require('./utils'); - -const NODE_ENV = process.env.NODE_ENV; -if (!NODE_ENV) { - console.error('NODE_ENV not set'); - process.exit(1); -} - -const builtModulesDir = resolve( - __dirname, - '..', - '..', - 'build', - 'oss-experimental', -); - -const __DEV__ = NODE_ENV === 'development'; - -const DEVTOOLS_VERSION = getVersionString(process.env.DEVTOOLS_VERSION); - -const IS_CHROME = process.env.IS_CHROME === 'true'; -const IS_FIREFOX = process.env.IS_FIREFOX === 'true'; -const IS_EDGE = process.env.IS_EDGE === 'true'; - -const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss'; - -module.exports = { - mode: __DEV__ ? 'development' : 'production', - devtool: false, - entry: { - backend: './src/backend.js', - }, - output: { - path: __dirname + '/build', - filename: 'react_devtools_backend_compact.js', - }, - node: { - global: false, - }, - resolve: { - alias: { - react: resolve(builtModulesDir, 'react'), - 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), - 'react-devtools-feature-flags': resolveFeatureFlags(featureFlagTarget), - 'react-dom': resolve(builtModulesDir, 'react-dom'), - 'react-is': resolve(builtModulesDir, 'react-is'), - scheduler: resolve(builtModulesDir, 'scheduler'), - }, - }, - optimization: { - minimize: !__DEV__, - minimizer: [ - new TerserPlugin({ - terserOptions: { - compress: { - unused: true, - dead_code: true, - }, - mangle: { - keep_fnames: true, - }, - format: { - comments: false, - }, - }, - extractComments: false, - }), - ], - }, - plugins: [ - new Webpack.ProvidePlugin({ - process: 'process/browser', - }), - new Webpack.DefinePlugin({ - __DEV__, - __PROFILE__: false, - // By importing `shared/` we may import ReactFeatureFlags - __EXPERIMENTAL__: true, - 'process.env.DEVTOOLS_PACKAGE': `"react-devtools-extensions"`, - 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`, - 'process.env.GITHUB_URL': `"${GITHUB_URL}"`, - 'process.env.IS_CHROME': IS_CHROME, - 'process.env.IS_FIREFOX': IS_FIREFOX, - 'process.env.IS_EDGE': IS_EDGE, - __IS_CHROME__: IS_CHROME, - __IS_FIREFOX__: IS_FIREFOX, - __IS_EDGE__: IS_EDGE, - __IS_NATIVE__: false, - __IS_INTERNAL_MCP_BUILD__: false, - }), - new Webpack.SourceMapDevToolPlugin({ - filename: '[file].map', - noSources: !__DEV__, - // https://github.com/webpack/webpack/issues/3603#issuecomment-1743147144 - moduleFilenameTemplate(info) { - const {absoluteResourcePath, namespace, resourcePath} = info; - - if (isAbsolute(absoluteResourcePath)) { - return relative(__dirname + '/build', absoluteResourcePath); - } - - // Mimic Webpack's default behavior: - return `webpack://${namespace}/${resourcePath}`; - }, - }), - new SourceMapIgnoreListPlugin({ - shouldIgnoreSource: () => !__DEV__, - }), - ], - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: { - configFile: resolve( - __dirname, - '..', - 'react-devtools-shared', - 'babel.config.js', - ), - }, - }, - ], - }, -}; diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js index 26580dd853969..5c03d85dd9315 100644 --- a/packages/react-devtools-extensions/webpack.config.js +++ b/packages/react-devtools-extensions/webpack.config.js @@ -66,6 +66,7 @@ module.exports = { mode: __DEV__ ? 'development' : 'production', devtool: false, entry: { + backend: './src/backend.js', background: './src/background/index.js', backendManager: './src/contentScripts/backendManager.js', fileFetcher: './src/contentScripts/fileFetcher.js', @@ -79,7 +80,14 @@ module.exports = { output: { path: __dirname + '/build', publicPath: '/build/', - filename: '[name].js', + filename: chunkData => { + switch (chunkData.chunk.name) { + case 'backend': + return 'react_devtools_backend_compact.js'; + default: + return '[name].js'; + } + }, chunkFilename: '[name].chunk.js', }, node: { @@ -141,7 +149,7 @@ module.exports = { }), new Webpack.SourceMapDevToolPlugin({ filename: '[file].map', - include: 'installHook.js', + include: ['installHook.js', 'react_devtools_backend_compact.js'], noSources: !__DEV__, // https://github.com/webpack/webpack/issues/3603#issuecomment-1743147144 moduleFilenameTemplate(info) { @@ -163,6 +171,7 @@ module.exports = { } const contentScriptNamesToIgnoreList = [ + 'react_devtools_backend_compact', // This is where we override console 'installHook', ]; From e3c9656d20618ed321aea85cb3d844cbd1dce078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Wed, 17 Sep 2025 10:52:02 -0400 Subject: [PATCH 5/7] Ensure Performance Track are Clamped and Don't overlap (#34509) This simplifies the logic for clamping the start times of various phases. Instead of checking in multiple places I ensure we compute a value for each phase that is then clamped to the next phase so they don't overlap. If they're zero they're not printed. I also added a name for all the anonymous labels. Those are mainly fillers for sync work that should be quick but it helps debugging if we can name them. Finally the real fix is to update the clamp time which previously could lead to overlapping entries for consecutive updates when a previous update never finalized before the next update. --- .../src/ReactFiberPerformanceTrack.js | 76 ++++++++++++++----- .../src/ReactProfilerTimer.js | 2 + .../__tests__/ReactProfiler-test.internal.js | 1 + 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 86f96d5d079aa..8fe2f2a8a95da 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -636,11 +636,25 @@ export function logBlockingStart( ): void { if (supportsUserTiming) { currentTrack = 'Blocking'; + // Clamp start times + if (updateTime > 0) { + if (updateTime > renderStartTime) { + updateTime = renderStartTime; + } + } else { + updateTime = renderStartTime; + } + if (eventTime > 0) { + if (eventTime > updateTime) { + eventTime = updateTime; + } + } else { + eventTime = updateTime; + } // If a blocking update was spawned within render or an effect, that's considered a cascading render. // If you have a second blocking update within the same event, that suggests multiple flushSync or // setState in a microtask which is also considered a cascade. - const eventEndTime = updateTime > 0 ? updateTime : renderStartTime; - if (eventTime > 0 && eventType !== null && eventEndTime > eventTime) { + if (eventType !== null && updateTime > eventTime) { // Log the time from the event timeStamp until we called setState. const color = eventIsRepeat ? 'secondary-light' : 'warning'; if (__DEV__ && debugTask) { @@ -648,9 +662,9 @@ export function logBlockingStart( // $FlowFixMe[method-unbinding] console.timeStamp.bind( console, - eventIsRepeat ? '' : 'Event: ' + eventType, + eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType, eventTime, - eventEndTime, + updateTime, currentTrack, LANES_TRACK_GROUP, color, @@ -658,16 +672,16 @@ export function logBlockingStart( ); } else { console.timeStamp( - eventIsRepeat ? '' : 'Event: ' + eventType, + eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType, eventTime, - eventEndTime, + updateTime, currentTrack, LANES_TRACK_GROUP, color, ); } } - if (updateTime > 0 && renderStartTime > updateTime) { + if (renderStartTime > updateTime) { // Log the time from when we called setState until we started rendering. const color = isSpawnedUpdate ? 'error' @@ -739,18 +753,39 @@ export function logTransitionStart( ): void { if (supportsUserTiming) { currentTrack = 'Transition'; - const eventEndTime = - startTime > 0 ? startTime : updateTime > 0 ? updateTime : renderStartTime; - if (eventTime > 0 && eventEndTime > eventTime && eventType !== null) { + // Clamp start times + if (updateTime > 0) { + if (updateTime > renderStartTime) { + updateTime = renderStartTime; + } + } else { + updateTime = renderStartTime; + } + if (startTime > 0) { + if (startTime > updateTime) { + startTime = updateTime; + } + } else { + startTime = updateTime; + } + if (eventTime > 0) { + if (eventTime > startTime) { + eventTime = startTime; + } + } else { + eventTime = startTime; + } + + if (startTime > eventTime && eventType !== null) { // Log the time from the event timeStamp until we started a transition. const color = eventIsRepeat ? 'secondary-light' : 'warning'; if (__DEV__ && debugTask) { debugTask.run( console.timeStamp.bind( console, - eventIsRepeat ? '' : 'Event: ' + eventType, + eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType, eventTime, - eventEndTime, + startTime, currentTrack, LANES_TRACK_GROUP, color, @@ -758,17 +793,16 @@ export function logTransitionStart( ); } else { console.timeStamp( - eventIsRepeat ? '' : 'Event: ' + eventType, + eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType, eventTime, - eventEndTime, + startTime, currentTrack, LANES_TRACK_GROUP, color, ); } } - const startEndTime = updateTime > 0 ? updateTime : renderStartTime; - if (startTime > 0 && startEndTime > startTime) { + if (updateTime > startTime) { // Log the time from when we started an async transition until we called setState or started rendering. // TODO: Ideally this would use the debugTask of the startTransition call perhaps. if (__DEV__ && debugTask) { @@ -778,7 +812,7 @@ export function logTransitionStart( console, 'Action', startTime, - startEndTime, + updateTime, currentTrack, LANES_TRACK_GROUP, 'primary-dark', @@ -788,14 +822,14 @@ export function logTransitionStart( console.timeStamp( 'Action', startTime, - startEndTime, + updateTime, currentTrack, LANES_TRACK_GROUP, 'primary-dark', ); } } - if (updateTime > 0 && renderStartTime > updateTime) { + if (renderStartTime > updateTime) { // Log the time from when we called setState until we started rendering. const label = isPingedUpdate ? 'Promise Resolved' @@ -1337,7 +1371,7 @@ export function logPaintYieldPhase( // $FlowFixMe[method-unbinding] console.timeStamp.bind( console, - delayedUntilPaint ? 'Waiting for Paint' : '', + delayedUntilPaint ? 'Waiting for Paint' : 'Waiting', startTime, endTime, currentTrack, @@ -1347,7 +1381,7 @@ export function logPaintYieldPhase( ); } else { console.timeStamp( - delayedUntilPaint ? 'Waiting for Paint' : '', + delayedUntilPaint ? 'Waiting for Paint' : 'Waiting', startTime, endTime, currentTrack, diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index a1779235fc02b..62b76ee6a407a 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -246,6 +246,7 @@ export function clearBlockingTimers(): void { blockingUpdateComponentName = null; blockingSuspendedTime = -1.1; blockingEventIsRepeat = true; + blockingClampTime = now(); } export function startAsyncTransitionTimer(): void { @@ -282,6 +283,7 @@ export function clearTransitionTimers(): void { transitionUpdateType = 0; transitionSuspendedTime = -1.1; transitionEventIsRepeat = true; + transitionClampTime = now(); } export function clampBlockingTimers(finalTime: number): void { diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index a8c00b2b562dc..ee1ec514e4d47 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -179,6 +179,7 @@ describe(`onRender`, () => { 'read current time', 'read current time', 'read current time', + 'read current time', ]); } else { assertLog([ From 128abcfa019c1282f59b3f274051da03a405ead2 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 17 Sep 2025 17:59:55 +0200 Subject: [PATCH 6/7] [DevTools] Don't inline workers for extensions (#34508) --- .../workflows/devtools_regression_tests.yml | 2 +- .github/workflows/runtime_build_and_test.yml | 6 +++++ .gitignore | 1 + .../react-devtools-core/webpack.standalone.js | 1 + .../src/main/index.js | 13 +++++----- .../webpack.config.js | 2 +- .../webpack.config.frontend.js | 1 + .../react-devtools-inline/webpack.config.js | 1 + scripts/ci/run_devtools_e2e_tests.js | 24 ++++++++++++------- 9 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml index 13b37f1743b45..9fe0c55e0bd00 100644 --- a/.github/workflows/devtools_regression_tests.yml +++ b/.github/workflows/devtools_regression_tests.yml @@ -201,5 +201,5 @@ jobs: - uses: actions/upload-artifact@v4 with: name: screenshots - path: ./tmp/screenshots + path: ./tmp/playwright-artifacts if-no-files-found: warn diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index 8ecb956e6b9c6..47293204ac390 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -831,6 +831,12 @@ jobs: - run: ./scripts/ci/run_devtools_e2e_tests.js env: RELEASE_CHANNEL: experimental + - name: Archive Playwright report + uses: actions/upload-artifact@v4 + with: + name: devtools-playwright-artifacts + path: tmp/playwright-artifacts + if-no-files-found: warn # ----- SIZEBOT ----- sizebot: diff --git a/.gitignore b/.gitignore index 6432df4f054d8..d2aefb620263b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ chrome-user-data .vscode *.swp *.swo +/tmp packages/react-devtools-core/dist packages/react-devtools-extensions/chrome/build diff --git a/packages/react-devtools-core/webpack.standalone.js b/packages/react-devtools-core/webpack.standalone.js index 6a9636c6911b1..6279ba7fb3498 100644 --- a/packages/react-devtools-core/webpack.standalone.js +++ b/packages/react-devtools-core/webpack.standalone.js @@ -108,6 +108,7 @@ module.exports = { { loader: 'workerize-loader', options: { + // Workers would have to be exposed on a public path in order to outline them. inline: true, name: '[name]', }, diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index 362793e3eb430..cec9fd6df454f 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -24,6 +24,7 @@ import { normalizeUrlIfValid, } from 'react-devtools-shared/src/utils'; import {checkConditions} from 'react-devtools-shared/src/devtools/views/Editor/utils'; +import * as parseHookNames from 'react-devtools-shared/src/hooks/parseHookNames'; import { setBrowserSelectionFromReact, @@ -40,6 +41,12 @@ import getProfilingFlags from './getProfilingFlags'; import debounce from './debounce'; import './requestAnimationFramePolyfill'; +const resolvedParseHookNames = Promise.resolve(parseHookNames); +// DevTools assumes this is a dynamically imported module. Since we outline +// workers in this bundle, we can sync require the module since it's just a thin +// wrapper around calling the worker. +const hookNamesModuleLoaderFunction = () => resolvedParseHookNames; + function createBridge() { bridge = new Bridge({ listen(fn) { @@ -188,12 +195,6 @@ function createBridgeAndStore() { ); }; - // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration. - const hookNamesModuleLoaderFunction = () => - import( - /* webpackChunkName: 'parseHookNames' */ 'react-devtools-shared/src/hooks/parseHookNames' - ); - root = createRoot(document.createElement('div')); render = (overrideTab = mostRecentOverrideTab) => { diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js index 5c03d85dd9315..4592363c64a91 100644 --- a/packages/react-devtools-extensions/webpack.config.js +++ b/packages/react-devtools-extensions/webpack.config.js @@ -261,7 +261,7 @@ module.exports = { { loader: 'workerize-loader', options: { - inline: true, + inline: false, name: '[name]', }, }, diff --git a/packages/react-devtools-fusebox/webpack.config.frontend.js b/packages/react-devtools-fusebox/webpack.config.frontend.js index 1266d3d82895e..0d56e81a34b6a 100644 --- a/packages/react-devtools-fusebox/webpack.config.frontend.js +++ b/packages/react-devtools-fusebox/webpack.config.frontend.js @@ -101,6 +101,7 @@ module.exports = { { loader: 'workerize-loader', options: { + // Workers would have to be exposed on a public path in order to outline them. inline: true, name: '[name]', }, diff --git a/packages/react-devtools-inline/webpack.config.js b/packages/react-devtools-inline/webpack.config.js index 358a18f0e7d49..a0300069bfa2e 100644 --- a/packages/react-devtools-inline/webpack.config.js +++ b/packages/react-devtools-inline/webpack.config.js @@ -93,6 +93,7 @@ module.exports = { { loader: 'workerize-loader', options: { + // Workers would have to be exposed on a public path in order to outline them. inline: true, name: '[name]', }, diff --git a/scripts/ci/run_devtools_e2e_tests.js b/scripts/ci/run_devtools_e2e_tests.js index 0d54e710b4d87..040af722ad9a4 100755 --- a/scripts/ci/run_devtools_e2e_tests.js +++ b/scripts/ci/run_devtools_e2e_tests.js @@ -9,7 +9,7 @@ const ROOT_PATH = join(__dirname, '..', '..'); const reactVersion = process.argv[2]; const inlinePackagePath = join(ROOT_PATH, 'packages', 'react-devtools-inline'); const shellPackagePath = join(ROOT_PATH, 'packages', 'react-devtools-shell'); -const screenshotPath = join(ROOT_PATH, 'tmp', 'screenshots'); +const playwrightArtifactsPath = join(ROOT_PATH, 'tmp', 'playwright-artifacts'); const {SUCCESSFUL_COMPILATION_MESSAGE} = require( join(shellPackagePath, 'constants.js') @@ -125,14 +125,22 @@ function runTestShell() { async function runEndToEndTests() { logBright('Running e2e tests'); if (!reactVersion) { - testProcess = spawn('yarn', ['test:e2e', `--output=${screenshotPath}`], { - cwd: inlinePackagePath, - }); + testProcess = spawn( + 'yarn', + ['test:e2e', `--output=${playwrightArtifactsPath}`], + { + cwd: inlinePackagePath, + } + ); } else { - testProcess = spawn('yarn', ['test:e2e', `--output=${screenshotPath}`], { - cwd: inlinePackagePath, - env: {...process.env, REACT_VERSION: reactVersion}, - }); + testProcess = spawn( + 'yarn', + ['test:e2e', `--output=${playwrightArtifactsPath}`], + { + cwd: inlinePackagePath, + env: {...process.env, REACT_VERSION: reactVersion}, + } + ); } testProcess.stdout.on('data', data => { From 84af9085c11411e44cc5e5aee6cf00c02a78986e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Wed, 17 Sep 2025 13:06:30 -0400 Subject: [PATCH 7/7] Log Performance Track Entries for View Transitions (#34510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stacked on #34509. View Transitions introduces a bunch of new types of gaps in the commit phase which needs to be logged differently in the performance track. One thing that can happen is that a `flushSync` update forces the View Transition to abort before it has started if it happens in the gap before the transition is ready. In that case we log "Interrupted View Transition". Otherwise, when we're done in `startViewTransition` there's some work to finalize the animations before the `ready` calllback. This is logged as "Starting Animation". Then there's a gap before the passive effects fire which we log as "Animating". This can be long unless they're forced to flush early e.g. due to another lane updating. The "Animating" track should then pick up which doesn't do yet. This one is tricky because this is after the actual commit phase and needs to be interrupted by new renders which themselves can be suspended on the animation finshing. This PR is just a subset of all the cases. Will need a lot more work. Screenshot 2025-09-16 at 10 19 06 PM --- .../src/ReactFiberPerformanceTrack.js | 86 ++++++++++++++- .../src/ReactFiberRootScheduler.js | 3 +- .../src/ReactFiberWorkLoop.js | 103 +++++++++++++----- 3 files changed, 161 insertions(+), 31 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 8fe2f2a8a95da..92ca7e00e2696 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -1320,6 +1320,7 @@ export function logCommitPhase( startTime: number, endTime: number, errors: null | Array>, + abortedViewTransition: boolean, debugTask: null | ConsoleTask, ): void { if (errors !== null) { @@ -1335,22 +1336,24 @@ export function logCommitPhase( // $FlowFixMe[method-unbinding] console.timeStamp.bind( console, - 'Commit', + abortedViewTransition + ? 'Commit Interrupted View Transition' + : 'Commit', startTime, endTime, currentTrack, LANES_TRACK_GROUP, - 'secondary-dark', + abortedViewTransition ? 'error' : 'secondary-dark', ), ); } else { console.timeStamp( - 'Commit', + abortedViewTransition ? 'Commit Interrupted View Transition' : 'Commit', startTime, endTime, currentTrack, LANES_TRACK_GROUP, - 'secondary-dark', + abortedViewTransition ? 'error' : 'secondary-dark', ); } } @@ -1392,6 +1395,81 @@ export function logPaintYieldPhase( } } +export function logStartViewTransitionYieldPhase( + startTime: number, + endTime: number, + abortedViewTransition: boolean, + debugTask: null | ConsoleTask, +): void { + if (supportsUserTiming) { + if (endTime <= startTime) { + return; + } + if (__DEV__ && debugTask) { + debugTask.run( + // $FlowFixMe[method-unbinding] + console.timeStamp.bind( + console, + abortedViewTransition + ? 'Interrupted View Transition' + : 'Starting Animation', + startTime, + endTime, + currentTrack, + LANES_TRACK_GROUP, + abortedViewTransition ? 'error' : 'secondary-light', + ), + ); + } else { + console.timeStamp( + abortedViewTransition + ? 'Interrupted View Transition' + : 'Starting Animation', + startTime, + endTime, + currentTrack, + LANES_TRACK_GROUP, + abortedViewTransition ? ' error' : 'secondary-light', + ); + } + } +} + +export function logAnimatingPhase( + startTime: number, + endTime: number, + debugTask: null | ConsoleTask, +): void { + if (supportsUserTiming) { + if (endTime <= startTime) { + return; + } + if (__DEV__ && debugTask) { + debugTask.run( + // $FlowFixMe[method-unbinding] + console.timeStamp.bind( + console, + 'Animating', + startTime, + endTime, + currentTrack, + LANES_TRACK_GROUP, + 'secondary', + ), + ); + } else { + console.timeStamp( + 'Animating', + startTime, + endTime, + currentTrack, + LANES_TRACK_GROUP, + 'secondary', + ); + } + } +} + export function logPassiveCommitPhase( startTime: number, endTime: number, diff --git a/packages/react-reconciler/src/ReactFiberRootScheduler.js b/packages/react-reconciler/src/ReactFiberRootScheduler.js index 3ed7ad7e2803a..26b6255227626 100644 --- a/packages/react-reconciler/src/ReactFiberRootScheduler.js +++ b/packages/react-reconciler/src/ReactFiberRootScheduler.js @@ -41,6 +41,7 @@ import { NoContext, RenderContext, flushPendingEffects, + flushPendingEffectsDelayed, getExecutionContext, getWorkInProgressRoot, getWorkInProgressRootRenderLanes, @@ -542,7 +543,7 @@ function performWorkOnRootViaSchedulerTask( // Flush any pending passive effects before deciding which lanes to work on, // in case they schedule additional work. const originalCallbackNode = root.callbackNode; - const didFlushPassiveEffects = flushPendingEffects(true); + const didFlushPassiveEffects = flushPendingEffectsDelayed(); if (didFlushPassiveEffects) { // Something in the passive effect phase may have canceled the current task. // Check if the task node for this root was changed. diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 79909cc25f104..d141c2855f66e 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -83,6 +83,8 @@ import { logSuspendedCommitPhase, logCommitPhase, logPaintYieldPhase, + logStartViewTransitionYieldPhase, + logAnimatingPhase, logPassiveCommitPhase, logYieldTime, logActionYieldTime, @@ -674,6 +676,11 @@ const IMMEDIATE_COMMIT = 0; const SUSPENDED_COMMIT = 1; const THROTTLED_COMMIT = 2; +type DelayedCommitReason = 0 | 1 | 2 | 3; +const ABORTED_VIEW_TRANSITION_COMMIT = 1; +const DELAYED_PASSIVE_COMMIT = 2; +const ANIMATION_STARTED_COMMIT = 3; + const NO_PENDING_EFFECTS = 0; const PENDING_MUTATION_PHASE = 1; const PENDING_LAYOUT_PHASE = 2; @@ -696,6 +703,7 @@ let pendingViewTransitionEvents: Array<(types: Array) => void> | null = let pendingTransitionTypes: null | TransitionTypes = null; let pendingDidIncludeRenderPhaseUpdate: boolean = false; let pendingSuspendedCommitReason: SuspendedCommitReason = IMMEDIATE_COMMIT; // Profiling-only +let pendingDelayedCommitReason: DelayedCommitReason = IMMEDIATE_COMMIT; // Profiling-only // Use these to prevent an infinite loop of nested updates const NESTED_UPDATE_LIMIT = 50; @@ -3436,6 +3444,7 @@ function commitRoot( if (enableProfilerTimer) { pendingEffectsRenderEndTime = completedRenderEndTime; pendingSuspendedCommitReason = suspendedCommitReason; + pendingDelayedCommitReason = IMMEDIATE_COMMIT; } if (enableGestureTransition && isGestureRender(lanes)) { @@ -3495,7 +3504,10 @@ function commitRoot( // event when logging events. trackSchedulerEvent(); } - flushPassiveEffects(true); + if (pendingDelayedCommitReason === IMMEDIATE_COMMIT) { + pendingDelayedCommitReason = DELAYED_PASSIVE_COMMIT; + } + flushPassiveEffects(); // This render triggered passive effects: release the root cache pool // *after* passive effects fire to avoid freeing a cache pool that may // be referenced by a node in the tree (HostRoot, Cache boundary etc) @@ -3736,6 +3748,23 @@ function flushLayoutEffects(): void { ReactSharedInternals.T = prevTransition; } } + + const completedRenderEndTime = pendingEffectsRenderEndTime; + const suspendedCommitReason = pendingSuspendedCommitReason; + + if (enableProfilerTimer && enableComponentPerformanceTrack) { + recordCommitEndTime(); + logCommitPhase( + suspendedCommitReason === IMMEDIATE_COMMIT + ? completedRenderEndTime + : commitStartTime, + commitEndTime, + commitErrors, + pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT, + workInProgressUpdateTask, + ); + } + pendingEffectsStatus = PENDING_AFTER_MUTATION_PHASE; } @@ -3748,6 +3777,25 @@ function flushSpawnedWork(): void { ) { return; } + if (enableProfilerTimer && enableComponentPerformanceTrack) { + // If we didn't skip the after mutation phase, when is means we started an animation. + const startedAnimation = pendingEffectsStatus === PENDING_SPAWNED_WORK; + if (startedAnimation) { + const startViewTransitionStartTime = commitEndTime; + // Update the new commitEndTime to when we started the animation. + recordCommitEndTime(); + logStartViewTransitionYieldPhase( + startViewTransitionStartTime, + commitEndTime, + pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT, + workInProgressUpdateTask, // TODO: Use a ViewTransition Task. + ); + if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) { + pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT; + } + } + } + pendingEffectsStatus = NO_PENDING_EFFECTS; pendingViewTransition = null; // The view transition has now fully started. @@ -3759,22 +3807,8 @@ function flushSpawnedWork(): void { const root = pendingEffectsRoot; const finishedWork = pendingFinishedWork; const lanes = pendingEffectsLanes; - const completedRenderEndTime = pendingEffectsRenderEndTime; const recoverableErrors = pendingRecoverableErrors; const didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate; - const suspendedCommitReason = pendingSuspendedCommitReason; - - if (enableProfilerTimer && enableComponentPerformanceTrack) { - recordCommitEndTime(); - logCommitPhase( - suspendedCommitReason === IMMEDIATE_COMMIT - ? completedRenderEndTime - : commitStartTime, - commitEndTime, - commitErrors, - workInProgressUpdateTask, - ); - } const passiveSubtreeMask = enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes) @@ -4141,7 +4175,14 @@ function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) { let didWarnAboutInterruptedViewTransitions = false; -export function flushPendingEffects(wasDelayedCommit?: boolean): boolean { +export function flushPendingEffectsDelayed(): boolean { + if (pendingDelayedCommitReason === IMMEDIATE_COMMIT) { + pendingDelayedCommitReason = DELAYED_PASSIVE_COMMIT; + } + return flushPendingEffects(); +} + +export function flushPendingEffects(): boolean { // Returns whether passive effects were flushed. if (enableViewTransition && pendingViewTransition !== null) { // If we forced a flush before the View Transition full started then we skip it. @@ -4159,6 +4200,7 @@ export function flushPendingEffects(wasDelayedCommit?: boolean): boolean { } } pendingViewTransition = null; + pendingDelayedCommitReason = ABORTED_VIEW_TRANSITION_COMMIT; } flushGestureMutations(); flushGestureAnimations(); @@ -4166,10 +4208,10 @@ export function flushPendingEffects(wasDelayedCommit?: boolean): boolean { flushLayoutEffects(); // Skip flushAfterMutation if we're forcing this early. flushSpawnedWork(); - return flushPassiveEffects(wasDelayedCommit); + return flushPassiveEffects(); } -function flushPassiveEffects(wasDelayedCommit?: boolean): boolean { +function flushPassiveEffects(): boolean { if (pendingEffectsStatus !== PENDING_PASSIVE_PHASE) { return false; } @@ -4194,7 +4236,7 @@ function flushPassiveEffects(wasDelayedCommit?: boolean): boolean { try { setCurrentUpdatePriority(priority); ReactSharedInternals.T = null; - return flushPassiveEffectsImpl(wasDelayedCommit); + return flushPassiveEffectsImpl(); } finally { setCurrentUpdatePriority(previousPriority); ReactSharedInternals.T = prevTransition; @@ -4206,7 +4248,7 @@ function flushPassiveEffects(wasDelayedCommit?: boolean): boolean { } } -function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) { +function flushPassiveEffectsImpl() { // Cache and clear the transitions flag const transitions = pendingPassiveTransitions; pendingPassiveTransitions = null; @@ -4246,12 +4288,21 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) { if (enableProfilerTimer && enableComponentPerformanceTrack) { resetCommitErrors(); passiveEffectStartTime = now(); - logPaintYieldPhase( - commitEndTime, - passiveEffectStartTime, - !!wasDelayedCommit, - workInProgressUpdateTask, - ); + if (pendingDelayedCommitReason === ANIMATION_STARTED_COMMIT) { + // The animation was started, so we've been animating since that happened. + logAnimatingPhase( + commitEndTime, + passiveEffectStartTime, + workInProgressUpdateTask, // TODO: Use a ViewTransition Task + ); + } else { + logPaintYieldPhase( + commitEndTime, + passiveEffectStartTime, + pendingDelayedCommitReason === DELAYED_PASSIVE_COMMIT, + workInProgressUpdateTask, + ); + } } if (enableSchedulingProfiler) {