1- import type { Compiler , RuleSetRule , RuleSetCondition } from 'webpack' ;
21import {
3- type ReactCompilerLoaderOption ,
42 defineReactCompilerLoaderOption ,
53 reactCompilerLoader ,
64} from 'react-compiler-webpack' ;
7- import type { Logger } from 'babel-plugin-react-compiler' ;
5+ import { validate } from 'schema-utils' ;
6+ import { schema } from './schema' ;
7+ import { ReactCompilerLogger } from './logger' ;
8+ import type {
9+ Compiler ,
10+ RuleSetRule ,
11+ ReactCompilerPluginOptions ,
12+ ResolvedReactCompilerPluginOptions ,
13+ ReactCompilerStats ,
14+ PanicThresholdOptions ,
15+ } from './types' ;
16+
17+ export type { ReactCompilerPluginOptions , ReactCompilerStats } from './types' ;
18+
19+ const NAME = 'ReactCompilerPlugin' ;
820
921/**
10- * React Compiler logger that tracks compilation statistics.
22+ * Default test pattern for matching React component files.
23+ * Matches .js, .jsx, .ts, .tsx, .mjs, .mts files,
24+ * excluding test, stories, and container files.
1125 */
12- class ReactCompilerLogger {
13- private compiledCount = 0 ;
14-
15- private skippedCount = 0 ;
16-
17- private errorCount = 0 ;
18-
19- private todoCount = 0 ;
20-
21- private compiledFiles : string [ ] = [ ] ;
22-
23- private skippedFiles : string [ ] = [ ] ;
24-
25- private errorFiles : string [ ] = [ ] ;
26-
27- private todoFiles : string [ ] = [ ] ;
28-
29- logEvent (
30- filename : string | null ,
31- event : { kind : string ; detail : { options : { category : string } } } ,
32- ) {
33- if ( filename === null ) {
34- return ;
35- }
36- const { options : errorDetails } = event . detail ?? { } ;
37- switch ( event . kind ) {
38- case 'CompileSuccess' :
39- this . compiledCount ++ ;
40- this . compiledFiles . push ( filename ) ;
41- console . log ( `✅ Compiled: ${ filename } ` ) ;
42- break ;
43- case 'CompileSkip' :
44- this . skippedCount ++ ;
45- this . skippedFiles . push ( filename ) ;
46- break ;
47- case 'CompileError' :
48- // This error is thrown for syntax that is not yet supported by the React Compiler.
49- // We count these separately as "unsupported" errors, since there's no actionable fix we can apply.
50- if ( errorDetails ?. category === 'Todo' ) {
51- this . todoCount ++ ;
52- this . todoFiles . push ( filename ) ;
53- break ;
54- }
55- this . errorCount ++ ;
56- this . errorFiles . push ( filename ) ;
57- console . error (
58- `❌ React Compiler error in ${ filename } : ${ errorDetails ? JSON . stringify ( errorDetails ) : 'Unknown error' } ` ,
59- ) ;
60- break ;
61- default :
62- break ;
63- }
64- }
65-
66- getStats ( ) {
67- return {
68- compiled : this . compiledCount ,
69- skipped : this . skippedCount ,
70- errors : this . errorCount ,
71- unsupported : this . todoCount ,
72- total :
73- this . compiledCount +
74- this . skippedCount +
75- this . errorCount +
76- this . todoCount ,
77- compiledFiles : this . compiledFiles ,
78- skippedFiles : this . skippedFiles ,
79- errorFiles : this . errorFiles ,
80- unsupportedFiles : this . todoFiles ,
81- } ;
82- }
26+ const DEFAULT_TEST =
27+ / (?: .(? ! \. (?: t e s t | s t o r i e s | c o n t a i n e r ) ) ) + \. (?: m ? [ j t ] s | [ j t ] s x ) $ / u;
8328
84- logSummary ( ) {
85- const stats = this . getStats ( ) ;
86- console . log ( '\n📊 React Compiler Statistics:' ) ;
87- console . log ( ` ✅ Compiled: ${ stats . compiled } files` ) ;
88- console . log ( ` ⏭️ Skipped: ${ stats . skipped } files` ) ;
89- console . log ( ` ❌ Errors: ${ stats . errors } files` ) ;
90- console . log ( ` 🔍 Unsupported: ${ stats . unsupported } files` ) ;
91- console . log ( ` 📦 Total processed: ${ stats . total } files` ) ;
92- }
29+ /**
30+ * Default options for the ReactCompilerPlugin.
31+ */
32+ const defaultOptions = {
33+ verbose : false ,
34+ debug : 'none' ,
35+ test : DEFAULT_TEST ,
36+ } as const satisfies Partial < ReactCompilerPluginOptions > ;
9337
94- reset ( ) {
95- this . compiledCount = 0 ;
96- this . skippedCount = 0 ;
97- this . errorCount = 0 ;
98- this . todoCount = 0 ;
99- this . compiledFiles = [ ] ;
100- this . skippedFiles = [ ] ;
101- this . errorFiles = [ ] ;
102- this . todoFiles = [ ] ;
38+ /**
39+ * Maps the simplified debug level to the panicThreshold option.
40+ *
41+ * @param debug - The debug level from plugin options
42+ * @returns The corresponding panicThreshold value
43+ */
44+ function mapDebugToPanicThreshold (
45+ debug : ReactCompilerPluginOptions [ 'debug' ] ,
46+ ) : PanicThresholdOptions {
47+ switch ( debug ) {
48+ case 'all' :
49+ return 'all_errors' ;
50+ case 'critical' :
51+ return 'critical_errors' ;
52+ case 'none' :
53+ default :
54+ return 'none' ;
10355 }
10456}
10557
106- export type ReactCompilerPluginOptions = {
107- /**
108- * The target React version for the compiler.
109- */
110- target : ReactCompilerLoaderOption [ 'target' ] ;
111- /**
112- * Enable verbose logging of compilation events.
113- */
114- verbose ?: boolean ;
115- /**
116- * Debug level for build failure behavior.
117- * - 'all': Fail build on and display debug information for all compilation errors.
118- * - 'critical': Fail build on and display debug information only for critical compilation errors.
119- * - 'none': Prevent build from failing.
120- */
121- debug ?: 'all' | 'critical' | 'none' ;
122- /**
123- * Regex pattern or patterns to include files for compilation.
124- * Files must match at least one pattern to be processed.
125- */
126- include ?: RuleSetCondition ;
127- /**
128- * Regex pattern or patterns to exclude files from compilation.
129- * Files matching any pattern will be skipped.
130- */
131- exclude ?: RuleSetCondition ;
132- /**
133- * Test pattern for matching files (e.g., /\.tsx?$/).
134- * Defaults to match .js, .jsx, .ts, .tsx, .mjs, .mts files.
135- */
136- test ?: RuleSetCondition ;
137- } ;
138-
13958/**
14059 * Webpack plugin that integrates React Compiler with logging and path filtering.
14160 *
61+ * This plugin provides:
62+ * - Automatic setup of the React Compiler webpack loader
63+ * - Configurable include/exclude patterns for file filtering
64+ * - Compilation statistics tracking and reporting
65+ * - Schema validation for plugin options
66+ * - Full support for all babel-plugin-react-compiler options
67+ *
14268 * @example
143- * ```ts
69+ * // Basic usage
14470 * const reactCompilerPlugin = new ReactCompilerPlugin({
14571 * target: '17',
14672 * verbose: true,
@@ -150,72 +76,134 @@ export type ReactCompilerPluginOptions = {
15076 * });
15177 *
15278 * // Add to webpack config
153- * plugins: [reactCompilerPlugin],
154- * module: {
155- * rules: [reactCompilerPlugin.getLoaderRule()],
156- * },
157- * ```
79+ * module.exports = {
80+ * plugins: [reactCompilerPlugin],
81+ * module: {
82+ * rules: [reactCompilerPlugin.getLoaderRule()],
83+ * },
84+ * };
85+ * @example
86+ * // With gating
87+ * const reactCompilerPlugin = new ReactCompilerPlugin({
88+ * target: '18',
89+ * gating: {
90+ * source: 'my-gating-module',
91+ * importSpecifierName: 'isCompilerEnabled',
92+ * },
93+ * });
94+ * @example
95+ * // With compilation mode
96+ * const reactCompilerPlugin = new ReactCompilerPlugin({
97+ * target: '19',
98+ * compilationMode: 'annotation', // Only compile annotated functions
99+ * });
158100 */
159101export class ReactCompilerPlugin {
160- private readonly options : Required < ReactCompilerPluginOptions > ;
102+ private readonly options : ResolvedReactCompilerPluginOptions ;
161103
162104 private readonly logger : ReactCompilerLogger ;
163105
164- static readonly defaultTest =
165- / (?: .(? ! \. (?: t e s t | s t o r i e s | c o n t a i n e r ) ) ) + \. (?: m ? [ j t ] s | [ j t ] s x ) $ / u;
106+ /**
107+ * Default test pattern for matching React component files.
108+ * Exposed for use in custom configurations.
109+ */
110+ static readonly defaultTest = DEFAULT_TEST ;
166111
167112 constructor ( options : ReactCompilerPluginOptions ) {
113+ validate ( schema , options , { name : NAME } ) ;
114+
168115 this . options = {
169- target : options . target ,
170- verbose : options . verbose ?? false ,
171- debug : options . debug ?? 'none' ,
172- include : options . include ?? / .* / u,
173- exclude : options . exclude ?? / (? ! ) / u, // Matches nothing by default
174- test : options . test ?? ReactCompilerPlugin . defaultTest ,
116+ ...defaultOptions ,
117+ ...options ,
175118 } ;
176119 this . logger = new ReactCompilerLogger ( ) ;
177120 }
178121
179122 /**
180123 * Returns the webpack loader configuration for the React Compiler.
181124 * Use this in your module.rules array.
125+ *
126+ * @returns A webpack RuleSetRule configured for React Compiler
182127 */
183128 getLoaderRule ( ) : RuleSetRule {
184- const { target, verbose, debug, include, exclude, test } = this . options ;
185-
186- const reactCompilerOptions = {
129+ const {
130+ target,
131+ verbose,
132+ debug,
133+ include,
134+ exclude,
135+ test,
136+ // Compiler options
137+ compilationMode,
138+ gating,
139+ dynamicGating,
140+ noEmit,
141+ eslintSuppressionRules,
142+ flowSuppressions,
143+ ignoreUseNoForget,
144+ customOptOutDirectives,
145+ sources,
146+ enableReanimatedCheck,
147+ // Babel options
148+ babelTransformOptions,
149+ } = this . options ;
150+
151+ const reactCompilerOptions = defineReactCompilerLoaderOption ( {
187152 target,
188- logger : verbose ? ( this . logger as Logger ) : undefined ,
189- panicThreshold : debug === 'none' ? debug : `${ debug } _errors` ,
190- } as const satisfies ReactCompilerLoaderOption ;
153+ logger : verbose ? this . logger : undefined ,
154+ panicThreshold : mapDebugToPanicThreshold ( debug ) ,
155+ // Pass through additional compiler options if provided
156+ ...( compilationMode !== undefined && { compilationMode } ) ,
157+ ...( gating !== undefined && { gating } ) ,
158+ ...( dynamicGating !== undefined && { dynamicGating } ) ,
159+ ...( noEmit !== undefined && { noEmit } ) ,
160+ ...( eslintSuppressionRules !== undefined && { eslintSuppressionRules } ) ,
161+ ...( flowSuppressions !== undefined && { flowSuppressions } ) ,
162+ ...( ignoreUseNoForget !== undefined && { ignoreUseNoForget } ) ,
163+ ...( customOptOutDirectives !== undefined && { customOptOutDirectives } ) ,
164+ ...( sources !== undefined && { sources } ) ,
165+ ...( enableReanimatedCheck !== undefined && { enableReanimatedCheck } ) ,
166+ ...( babelTransformOptions !== undefined && {
167+ babelTransFormOpt : babelTransformOptions ,
168+ } ) ,
169+ } ) ;
191170
192171 return {
193172 test,
194- include,
195- exclude,
173+ ... ( include !== undefined && { include } ) ,
174+ ... ( exclude !== undefined && { exclude } ) ,
196175 use : [
197176 {
198177 loader : reactCompilerLoader ,
199- options : defineReactCompilerLoaderOption ( reactCompilerOptions ) ,
178+ options : reactCompilerOptions ,
200179 } ,
201180 ] ,
202181 } ;
203182 }
204183
205184 /**
206185 * Get compilation statistics from the logger.
186+ *
187+ * @returns Current compilation statistics
207188 */
208- getStats ( ) {
189+ getStats ( ) : ReactCompilerStats {
209190 return this . logger . getStats ( ) ;
210191 }
211192
212193 /**
213194 * Reset the logger statistics.
195+ * Useful for programmatic access between builds.
214196 */
215- resetStats ( ) {
197+ resetStats ( ) : void {
216198 this . logger . reset ( ) ;
217199 }
218200
201+ /**
202+ * Webpack plugin apply method.
203+ * Hooks into the compilation lifecycle to log summaries and reset stats.
204+ *
205+ * @param compiler - The webpack compiler instance
206+ */
219207 apply ( compiler : Compiler ) : void {
220208 compiler . hooks . afterEmit . tap ( ReactCompilerPlugin . name , ( ) => {
221209 const logger = getReactCompilerLogger ( ) ;
0 commit comments