Skip to content

Commit 614b3d4

Browse files
committed
Extract logger, schema files
1 parent c37755f commit 614b3d4

File tree

3 files changed

+519
-158
lines changed

3 files changed

+519
-158
lines changed

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

Lines changed: 119 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,45 @@
1-
import type { Compiler, RuleSetRule, RuleSetCondition } from 'webpack';
1+
import type { Compiler } from 'webpack';
22
import {
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+
import type { ReactCompilerStats } from './types';
11+
12+
const NAME = 'ReactCompilerPlugin';
813

914
/**
10-
* React Compiler logger that tracks compilation statistics.
15+
* Default test pattern for matching React component files.
16+
* Matches .js, .jsx, .ts, .tsx, .mjs, .mts files,
17+
* excluding test, stories, and container files.
1118
*/
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-
}
19+
const DEFAULT_TEST =
20+
/(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/u;
10521

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-
};
22+
/**
23+
* Default options for the ReactCompilerPlugin.
24+
*/
25+
const defaultOptions = {
26+
verbose: false,
27+
debug: 'none',
28+
test: DEFAULT_TEST,
29+
} as const;
13830

13931
/**
14032
* Webpack plugin that integrates React Compiler with logging and path filtering.
14133
*
34+
* This plugin provides:
35+
* - Automatic setup of the React Compiler webpack loader
36+
* - Configurable include/exclude patterns for file filtering
37+
* - Compilation statistics tracking and reporting
38+
* - Schema validation for plugin options
39+
* - Full support for all babel-plugin-react-compiler options
40+
*
14241
* @example
143-
* ```ts
42+
* // Basic usage
14443
* const reactCompilerPlugin = new ReactCompilerPlugin({
14544
* target: '17',
14645
* verbose: true,
@@ -150,75 +49,137 @@ export type ReactCompilerPluginOptions = {
15049
* });
15150
*
15251
* // Add to webpack config
153-
* plugins: [reactCompilerPlugin],
154-
* module: {
155-
* rules: [reactCompilerPlugin.getLoaderRule()],
156-
* },
157-
* ```
52+
* module.exports = {
53+
* plugins: [reactCompilerPlugin],
54+
* module: {
55+
* rules: [reactCompilerPlugin.getLoaderRule()],
56+
* },
57+
* };
58+
* @example
59+
* // With gating
60+
* const reactCompilerPlugin = new ReactCompilerPlugin({
61+
* target: '18',
62+
* gating: {
63+
* source: 'my-gating-module',
64+
* importSpecifierName: 'isCompilerEnabled',
65+
* },
66+
* });
67+
* @example
68+
* // With compilation mode
69+
* const reactCompilerPlugin = new ReactCompilerPlugin({
70+
* target: '19',
71+
* compilationMode: 'annotation', // Only compile annotated functions
72+
* });
15873
*/
15974
export class ReactCompilerPlugin {
160-
private readonly options: Required<ReactCompilerPluginOptions>;
75+
private readonly options: ReactCompilerLoaderOption;
16176

16277
private readonly logger: ReactCompilerLogger;
16378

164-
static readonly defaultTest =
165-
/(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/u;
79+
/**
80+
* Default test pattern for matching React component files.
81+
* Exposed for use in custom configurations.
82+
*/
83+
static readonly defaultTest = DEFAULT_TEST;
84+
85+
constructor(options: ReactCompilerLoaderOption) {
86+
validate(schema, options, { name: NAME });
16687

167-
constructor(options: ReactCompilerPluginOptions) {
16888
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,
89+
...defaultOptions,
90+
...options,
17591
};
17692
this.logger = new ReactCompilerLogger();
17793
}
17894

17995
/**
18096
* Returns the webpack loader configuration for the React Compiler.
18197
* Use this in your module.rules array.
98+
*
99+
* @returns A webpack RuleSetRule configured for React Compiler
182100
*/
183-
getLoaderRule(): RuleSetRule {
184-
const { target, verbose, debug, include, exclude, test } = this.options;
185-
186-
const reactCompilerOptions = {
101+
getLoaderRule() {
102+
const {
187103
target,
188-
logger: verbose ? (this.logger as Logger) : undefined,
189-
panicThreshold: debug === 'none' ? debug : `${debug}_errors`,
190-
} as const satisfies ReactCompilerLoaderOption;
104+
verbose,
105+
debug,
106+
include,
107+
exclude,
108+
test,
109+
// Compiler options
110+
compilationMode,
111+
gating,
112+
dynamicGating,
113+
noEmit,
114+
eslintSuppressionRules,
115+
flowSuppressions,
116+
ignoreUseNoForget,
117+
customOptOutDirectives,
118+
sources,
119+
enableReanimatedCheck,
120+
// Babel options
121+
babelTransformOptions,
122+
} = this.options;
123+
124+
const reactCompilerOptions = defineReactCompilerLoaderOption({
125+
target,
126+
logger: verbose ? this.logger : undefined,
127+
panicThreshold: debug === 'none' ? undefined : `${debug}_errors`,
128+
// Pass through additional compiler options if provided
129+
...(compilationMode !== undefined && { compilationMode }),
130+
...(gating !== undefined && { gating }),
131+
...(dynamicGating !== undefined && { dynamicGating }),
132+
...(noEmit !== undefined && { noEmit }),
133+
...(eslintSuppressionRules !== undefined && { eslintSuppressionRules }),
134+
...(flowSuppressions !== undefined && { flowSuppressions }),
135+
...(ignoreUseNoForget !== undefined && { ignoreUseNoForget }),
136+
...(customOptOutDirectives !== undefined && { customOptOutDirectives }),
137+
...(sources !== undefined && { sources }),
138+
...(enableReanimatedCheck !== undefined && { enableReanimatedCheck }),
139+
...(babelTransformOptions !== undefined && {
140+
babelTransFormOpt: babelTransformOptions,
141+
}),
142+
});
191143

192144
return {
193145
test,
194-
include,
195-
exclude,
146+
...(include !== undefined && { include }),
147+
...(exclude !== undefined && { exclude }),
196148
use: [
197149
{
198150
loader: reactCompilerLoader,
199-
options: defineReactCompilerLoaderOption(reactCompilerOptions),
151+
options: reactCompilerOptions,
200152
},
201153
],
202154
};
203155
}
204156

205157
/**
206158
* Get compilation statistics from the logger.
159+
*
160+
* @returns Current compilation statistics
207161
*/
208-
getStats() {
162+
getStats(): ReactCompilerStats {
209163
return this.logger.getStats();
210164
}
211165

212166
/**
213167
* Reset the logger statistics.
168+
* Useful for programmatic access between builds.
214169
*/
215-
resetStats() {
170+
resetStats(): void {
216171
this.logger.reset();
217172
}
218173

174+
/**
175+
* Webpack plugin apply method.
176+
* Hooks into the compilation lifecycle to log summaries and reset stats.
177+
*
178+
* @param compiler - The webpack compiler instance
179+
*/
219180
apply(compiler: Compiler): void {
220181
compiler.hooks.afterEmit.tap(ReactCompilerPlugin.name, () => {
221-
const logger = getReactCompilerLogger();
182+
const logger = new ReactCompilerLogger();
222183
logger.logSummary();
223184
// Reset statistics after logging to prevent accumulation in watch mode
224185
logger.reset();

0 commit comments

Comments
 (0)