1- import type { Compiler , RuleSetRule , RuleSetCondition } from 'webpack' ;
1+ import type { Compiler } from 'webpack' ;
22import {
3- type ReactCompilerLoaderOption ,
43 defineReactCompilerLoaderOption ,
54 reactCompilerLoader ,
5+ type ReactCompilerLoaderOption ,
66} from 'react-compiler-webpack' ;
7- import type { Logger } from 'babel-plugin-react-compiler' ;
7+ import { validate } from 'schema-utils' ;
8+ import { schema } from './schema' ;
9+ import { ReactCompilerLogger } from './logger' ;
10+
11+ const NAME = 'ReactCompilerPlugin' ;
812
913/**
10- * React Compiler logger that tracks compilation statistics.
14+ * Default test pattern for matching React component files.
15+ * Matches .js, .jsx, .ts, .tsx, .mjs, .mts files,
16+ * excluding test, stories, and container files.
1117 */
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- }
83-
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- }
93-
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 = [ ] ;
103- }
104- }
18+ const DEFAULT_TEST =
19+ / (?: .(? ! \. (?: 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;
10520
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- } ;
21+ /**
22+ * Default options for the ReactCompilerPlugin.
23+ */
24+ const defaultOptions = {
25+ verbose : false ,
26+ debug : 'none' ,
27+ test : DEFAULT_TEST ,
28+ } as const ;
13829
13930/**
14031 * Webpack plugin that integrates React Compiler with logging and path filtering.
14132 *
33+ * This plugin provides:
34+ * - Automatic setup of the React Compiler webpack loader
35+ * - Configurable include/exclude patterns for file filtering
36+ * - Compilation statistics tracking and reporting
37+ * - Schema validation for plugin options
38+ * - Full support for all babel-plugin-react-compiler options
39+ *
14240 * @example
143- * ```ts
41+ * // Basic usage
14442 * const reactCompilerPlugin = new ReactCompilerPlugin({
14543 * target: '17',
14644 * verbose: true,
@@ -150,78 +48,121 @@ export type ReactCompilerPluginOptions = {
15048 * });
15149 *
15250 * // Add to webpack config
153- * plugins: [reactCompilerPlugin],
154- * module: {
155- * rules: [reactCompilerPlugin.getLoaderRule()],
156- * },
157- * ```
51+ * module.exports = {
52+ * plugins: [reactCompilerPlugin],
53+ * module: {
54+ * rules: [reactCompilerPlugin.getLoaderRule()],
55+ * },
56+ * };
57+ * @example
58+ * // With gating
59+ * const reactCompilerPlugin = new ReactCompilerPlugin({
60+ * target: '18',
61+ * gating: {
62+ * source: 'my-gating-module',
63+ * importSpecifierName: 'isCompilerEnabled',
64+ * },
65+ * });
66+ * @example
67+ * // With compilation mode
68+ * const reactCompilerPlugin = new ReactCompilerPlugin({
69+ * target: '19',
70+ * compilationMode: 'annotation', // Only compile annotated functions
71+ * });
15872 */
15973export class ReactCompilerPlugin {
160- private readonly options : Required < ReactCompilerPluginOptions > ;
74+ private readonly options : ReactCompilerLoaderOption ;
16175
16276 private readonly logger : ReactCompilerLogger ;
16377
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;
78+ /**
79+ * Default test pattern for matching React component files.
80+ * Exposed for use in custom configurations.
81+ */
82+ static readonly defaultTest = DEFAULT_TEST ;
83+
84+ constructor ( options : ReactCompilerLoaderOption ) {
85+ validate ( schema , options , { name : NAME } ) ;
16686
167- constructor ( options : ReactCompilerPluginOptions ) {
16887 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 ,
88+ ...defaultOptions ,
89+ ...options ,
17590 } ;
17691 this . logger = new ReactCompilerLogger ( ) ;
17792 }
17893
17994 /**
18095 * Returns the webpack loader configuration for the React Compiler.
18196 * Use this in your module.rules array.
97+ *
98+ * @returns A webpack RuleSetRule configured for React Compiler
18299 */
183- getLoaderRule ( ) : RuleSetRule {
184- const { target, verbose, debug, include, exclude, test } = this . options ;
185-
186- const reactCompilerOptions = {
100+ getLoaderRule ( ) {
101+ const {
187102 target,
188- logger : verbose ? ( this . logger as Logger ) : undefined ,
189- panicThreshold : debug === 'none' ? debug : `${ debug } _errors` ,
190- } as const satisfies ReactCompilerLoaderOption ;
103+ verbose,
104+ debug,
105+ include,
106+ exclude,
107+ test,
108+ // Compiler options
109+ compilationMode,
110+ gating,
111+ dynamicGating,
112+ noEmit,
113+ eslintSuppressionRules,
114+ flowSuppressions,
115+ ignoreUseNoForget,
116+ customOptOutDirectives,
117+ sources,
118+ enableReanimatedCheck,
119+ // Babel options
120+ babelTransformOptions,
121+ } = this . options ;
122+
123+ const reactCompilerOptions = defineReactCompilerLoaderOption ( {
124+ target,
125+ logger : verbose ? this . logger : undefined ,
126+ panicThreshold : debug === 'none' ? undefined : `${ debug } _errors` ,
127+ // Pass through additional compiler options if provided
128+ ...( compilationMode !== undefined && { compilationMode } ) ,
129+ ...( gating !== undefined && { gating } ) ,
130+ ...( dynamicGating !== undefined && { dynamicGating } ) ,
131+ ...( noEmit !== undefined && { noEmit } ) ,
132+ ...( eslintSuppressionRules !== undefined && { eslintSuppressionRules } ) ,
133+ ...( flowSuppressions !== undefined && { flowSuppressions } ) ,
134+ ...( ignoreUseNoForget !== undefined && { ignoreUseNoForget } ) ,
135+ ...( customOptOutDirectives !== undefined && { customOptOutDirectives } ) ,
136+ ...( sources !== undefined && { sources } ) ,
137+ ...( enableReanimatedCheck !== undefined && { enableReanimatedCheck } ) ,
138+ ...( babelTransformOptions !== undefined && {
139+ babelTransFormOpt : babelTransformOptions ,
140+ } ) ,
141+ } ) ;
191142
192143 return {
193144 test,
194- include,
195- exclude,
145+ ... ( include !== undefined && { include } ) ,
146+ ... ( exclude !== undefined && { exclude } ) ,
196147 use : [
197148 {
198149 loader : reactCompilerLoader ,
199- options : defineReactCompilerLoaderOption ( reactCompilerOptions ) ,
150+ options : reactCompilerOptions ,
200151 } ,
201152 ] ,
202153 } ;
203154 }
204155
205156 /**
206- * Get compilation statistics from the logger.
157+ * Webpack plugin apply method.
158+ * Hooks into the compilation lifecycle to log summaries and reset stats.
159+ *
160+ * @param compiler - The webpack compiler instance
207161 */
208- getStats ( ) {
209- return this . logger . getStats ( ) ;
210- }
211-
212- /**
213- * Reset the logger statistics.
214- */
215- resetStats ( ) {
216- this . logger . reset ( ) ;
217- }
218-
219162 apply ( compiler : Compiler ) : void {
220163 compiler . hooks . afterEmit . tap ( ReactCompilerPlugin . name , ( ) => {
221- const logger = getReactCompilerLogger ( ) ;
222- logger . logSummary ( ) ;
223- // Reset statistics after logging to prevent accumulation in watch mode
224- logger . reset ( ) ;
164+ this . logger . logSummary ( ) ;
165+ this . logger . reset ( ) ;
225166 } ) ;
226167 }
227168}
0 commit comments