-
Notifications
You must be signed in to change notification settings - Fork 5.4k
build: Enable React Compiler for Webpack builds #38007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 25 commits
ff3e99a
27301e3
11907d8
95bc187
6037088
7f6626d
50fa7b3
13a52f8
943035d
29b7626
87b31a7
bfa3091
5fe6afd
6a0635c
0e67907
4777391
83d86b9
6f381b1
38c2fb6
e5e61db
e785706
1444569
b560c3e
986c815
52857d7
e7c11bf
0b3aa55
00b19d0
fdba65b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| import { | ||
| type ReactCompilerLoaderOption, | ||
| defineReactCompilerLoaderOption, | ||
| reactCompilerLoader, | ||
| } from 'react-compiler-webpack'; | ||
| import type { Logger } from 'babel-plugin-react-compiler'; | ||
|
|
||
| /** | ||
| * React Compiler logger that tracks compilation statistics | ||
| */ | ||
| class ReactCompilerLogger { | ||
| private compiledCount = 0; | ||
|
|
||
| private skippedCount = 0; | ||
|
|
||
| private errorCount = 0; | ||
|
|
||
| private todoCount = 0; | ||
|
|
||
| private compiledFiles: string[] = []; | ||
|
|
||
| private skippedFiles: string[] = []; | ||
|
|
||
| private errorFiles: string[] = []; | ||
|
|
||
| private todoFiles: string[] = []; | ||
|
|
||
| logEvent( | ||
| filename: string | null, | ||
| event: { kind: string; detail: { options: { category: string } } }, | ||
| ) { | ||
| if (filename === null) { | ||
| return; | ||
| } | ||
| const { options: errorDetails } = event.detail ?? {}; | ||
| switch (event.kind) { | ||
| case 'CompileSuccess': | ||
| this.compiledCount++; | ||
| this.compiledFiles.push(filename); | ||
| console.log(`✅ Compiled: ${filename}`); | ||
| break; | ||
| case 'CompileSkip': | ||
| this.skippedCount++; | ||
| this.skippedFiles.push(filename); | ||
| break; | ||
| case 'CompileError': | ||
| // This error is thrown for syntax that is not yet supported by the React Compiler. | ||
| // We count these separately as "unsupported" errors, since there's no actionable fix we can apply. | ||
| if (errorDetails?.category === 'Todo') { | ||
| this.todoCount++; | ||
| this.todoFiles.push(filename); | ||
| break; | ||
| } | ||
| this.errorCount++; | ||
| this.errorFiles.push(filename); | ||
| console.error( | ||
| `❌ React Compiler error in ${filename}: ${errorDetails ? JSON.stringify(errorDetails) : 'Unknown error'}`, | ||
| ); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| getStats() { | ||
| return { | ||
| compiled: this.compiledCount, | ||
| skipped: this.skippedCount, | ||
| errors: this.errorCount, | ||
| unsupported: this.todoCount, | ||
| total: | ||
| this.compiledCount + | ||
| this.skippedCount + | ||
| this.errorCount + | ||
| this.todoCount, | ||
| compiledFiles: this.compiledFiles, | ||
| skippedFiles: this.skippedFiles, | ||
| errorFiles: this.errorFiles, | ||
| unsupportedFiles: this.todoFiles, | ||
| }; | ||
| } | ||
|
|
||
| logSummary() { | ||
| const stats = this.getStats(); | ||
| console.log('\n📊 React Compiler Statistics:'); | ||
| console.log(` ✅ Compiled: ${stats.compiled} files`); | ||
| console.log(` ⏭️ Skipped: ${stats.skipped} files`); | ||
| console.log(` ❌ Errors: ${stats.errors} files`); | ||
| console.log(` 🔍 Unsupported: ${stats.unsupported} files`); | ||
| console.log(` 📦 Total processed: ${stats.total} files`); | ||
| } | ||
|
|
||
| /** | ||
| * Reset all statistics. Should be called after each build in watch mode | ||
| * to prevent accumulation across rebuilds. | ||
| */ | ||
| reset() { | ||
| this.compiledCount = 0; | ||
| this.skippedCount = 0; | ||
| this.errorCount = 0; | ||
| this.todoCount = 0; | ||
| this.compiledFiles = []; | ||
| this.skippedFiles = []; | ||
| this.errorFiles = []; | ||
| this.todoFiles = []; | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Logger statistics accumulate indefinitely in watch modeThe Additional Locations (1)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. Fixed: b560c3e |
||
|
|
||
| const reactCompilerLogger = new ReactCompilerLogger(); | ||
|
|
||
| /** | ||
| * Get the React Compiler logger singleton instance to access statistics. | ||
| */ | ||
| export function getReactCompilerLogger(): ReactCompilerLogger { | ||
| return reactCompilerLogger; | ||
| } | ||
|
|
||
| /** | ||
| * Get the React Compiler loader. | ||
| * | ||
| * @param target - The target version of the React Compiler. | ||
| * @param verbose - Whether to enable verbose mode. | ||
| * @param debug - The debug level to use. | ||
| * - 'all': Fail build on and display debug information for all compilation errors. | ||
| * - 'critical': Fail build on and display debug information only for critical compilation errors. | ||
| * - 'none': Prevent build from failing. | ||
| * @returns The React Compiler loader object with the loader and configured options. | ||
| */ | ||
| export const getReactCompilerLoader = ( | ||
| target: ReactCompilerLoaderOption['target'], | ||
| verbose: boolean, | ||
| debug: 'all' | 'critical' | 'none', | ||
| ) => { | ||
| const reactCompilerOptions = { | ||
| target, | ||
| logger: verbose ? (reactCompilerLogger as Logger) : undefined, | ||
| panicThreshold: debug === 'none' ? debug : `${debug}_errors`, | ||
|
||
| } as const satisfies ReactCompilerLoaderOption; | ||
|
|
||
| return { | ||
| loader: reactCompilerLoader, | ||
| options: defineReactCompilerLoaderOption(reactCompilerOptions), | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import type { Compiler } from 'webpack'; | ||
| import { getReactCompilerLogger } from '../../loaders/reactCompilerLoader'; | ||
|
|
||
| export class ReactCompilerPlugin { | ||
| apply(compiler: Compiler): void { | ||
| compiler.hooks.afterEmit.tap(ReactCompilerPlugin.name, () => { | ||
| const logger = getReactCompilerLogger(); | ||
| logger.logSummary(); | ||
| // Reset statistics after logging to prevent accumulation in watch mode | ||
| logger.reset(); | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,11 +28,13 @@ import { | |
| __HMR_READY__, | ||
| SNOW_MODULE_RE, | ||
| TREZOR_MODULE_RE, | ||
| UI_DIR_RE, | ||
| } from './utils/helpers'; | ||
| import { transformManifest } from './utils/plugins/ManifestPlugin/helpers'; | ||
| import { parseArgv, getDryRunMessage } from './utils/cli'; | ||
| import { getCodeFenceLoader } from './utils/loaders/codeFenceLoader'; | ||
| import { getSwcLoader } from './utils/loaders/swcLoader'; | ||
| import { getReactCompilerLoader } from './utils/loaders/reactCompilerLoader'; | ||
| import { getVariables } from './utils/config'; | ||
| import { ManifestPlugin } from './utils/plugins/ManifestPlugin'; | ||
| import { getLatestCommit } from './utils/git'; | ||
|
|
@@ -211,13 +213,25 @@ if (args.progress) { | |
| const { ProgressPlugin } = require('webpack'); | ||
| plugins.push(new ProgressPlugin()); | ||
| } | ||
| if (args.reactCompilerVerbose) { | ||
| const { | ||
| ReactCompilerPlugin, | ||
| } = require('./utils/plugins/ReactCompilerPlugin'); | ||
| plugins.push(new ReactCompilerPlugin()); | ||
| } | ||
|
|
||
| // #endregion plugins | ||
|
|
||
| const swcConfig = { args, browsersListQuery, isDevelopment }; | ||
| const tsxLoader = getSwcLoader('typescript', true, safeVariables, swcConfig); | ||
| const jsxLoader = getSwcLoader('ecmascript', true, safeVariables, swcConfig); | ||
| const npmLoader = getSwcLoader('ecmascript', false, {}, swcConfig); | ||
| const cjsLoader = getSwcLoader('ecmascript', false, {}, swcConfig, 'commonjs'); | ||
| const reactCompilerLoader = getReactCompilerLoader( | ||
| '17', | ||
| args.reactCompilerVerbose, | ||
| args.reactCompilerDebug, | ||
| ); | ||
|
|
||
| const config = { | ||
| entry, | ||
|
|
@@ -320,6 +334,11 @@ const config = { | |
| dependency: 'url', | ||
| type: 'asset/resource', | ||
| }, | ||
| { | ||
| test: /(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/u, | ||
|
||
| include: UI_DIR_RE, | ||
| use: [reactCompilerLoader], | ||
| }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: React Compiler loader missing required loadersThe React Compiler loader rule only applies
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: React Compiler loader not chained after SWC transformsThe React Compiler loader rule applies
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed here: #38007 (comment)
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // own typescript, and own typescript with jsx | ||
| { | ||
| test: /\.(?:ts|mts|tsx)$/u, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: UI_DIR_RE path construction misses app directory
The
UI_DIR_REregex resolves to/path/to/metamask-extension/ui/..., but webpack serves files from/path/to/metamask-extension/app/ui/.... The path construction injoin(__dirname, '../../../')should bejoin(__dirname, '../../../../app/')to correctly match the webpack context where files are actually located.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not true in our project.
app/ui/does not exist.