diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml index 0b70cfaf4e9ff..9fe0c55e0bd00 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 @@ -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 d9fb47da3b204..47293204ac390 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 @@ -826,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/build.js b/packages/react-devtools-extensions/build.js index 4d95af2a0a1aa..1c8322cd5c98e 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 @@ -66,22 +66,31 @@ 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); 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 +129,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 +141,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 +177,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/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.backend.js b/packages/react-devtools-extensions/webpack.backend.js deleted file mode 100644 index 4bfa05183067e..0000000000000 --- a/packages/react-devtools-extensions/webpack.backend.js +++ /dev/null @@ -1,118 +0,0 @@ -'use strict'; - -const {resolve, isAbsolute, relative} = require('path'); -const Webpack = require('webpack'); - -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: false, - }, - plugins: [ - new Webpack.ProvidePlugin({ - process: 'process/browser', - }), - new Webpack.DefinePlugin({ - __DEV__: true, - __PROFILE__: false, - __DEV____DEV__: true, - // 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 4a3052517c851..4592363c64a91 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, @@ -50,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', @@ -63,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: { @@ -103,7 +127,6 @@ module.exports = { plugins: [ new Webpack.ProvidePlugin({ process: 'process/browser', - Buffer: ['buffer', 'Buffer'], }), new Webpack.DefinePlugin({ __DEV__, @@ -126,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) { @@ -148,6 +171,7 @@ module.exports = { } const contentScriptNamesToIgnoreList = [ + 'react_devtools_backend_compact', // This is where we override console 'installHook', ]; @@ -213,6 +237,10 @@ module.exports = { ); }, }, + new StatsWriterPlugin({ + stats: 'verbose', + filename: statsFileName, + }), ], module: { defaultRules: [ @@ -233,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 ea04f4dad2d0d..0d56e81a34b6a 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__, @@ -102,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 9fa900dfa65f2..a0300069bfa2e 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__, @@ -94,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/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/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 86f96d5d079aa..92ca7e00e2696 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' @@ -1286,6 +1320,7 @@ export function logCommitPhase( startTime: number, endTime: number, errors: null | Array>, + abortedViewTransition: boolean, debugTask: null | ConsoleTask, ): void { if (errors !== null) { @@ -1301,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', ); } } @@ -1337,7 +1374,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 +1384,7 @@ export function logPaintYieldPhase( ); } else { console.timeStamp( - delayedUntilPaint ? 'Waiting for Paint' : '', + delayedUntilPaint ? 'Waiting for Paint' : 'Waiting', startTime, endTime, currentTrack, @@ -1358,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) { 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([ 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/scripts/ci/run_devtools_e2e_tests.js b/scripts/ci/run_devtools_e2e_tests.js index ca28ba85b2076..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') @@ -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}`); @@ -121,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 => { diff --git a/yarn.lock b/yarn.lock index f58b4979fa33d..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" @@ -17792,6 +17696,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" @@ -17980,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== @@ -17998,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"