Skip to content

Commit 856dc12

Browse files
committed
Extract logger, schema files
1 parent c37755f commit 856dc12

File tree

3 files changed

+512
-171
lines changed

3 files changed

+512
-171
lines changed
Lines changed: 112 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,44 @@
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+
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+
/(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/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
*/
15973
export 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-
/(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/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

Comments
 (0)