Skip to content

Commit 331bfa0

Browse files
committed
Define separate modules for logger, types, schema
1 parent 7d421b8 commit 331bfa0

File tree

4 files changed

+833
-152
lines changed

4 files changed

+833
-152
lines changed

development/webpack/utils/plugins/ReactCompilerPlugin/index.ts

Lines changed: 140 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,72 @@
1-
import type { Compiler, RuleSetRule, RuleSetCondition } from 'webpack';
21
import {
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+
/(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/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
*/
159101
export 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-
/(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/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

Comments
 (0)