Skip to content

Commit 7d421b8

Browse files
committed
Refactor reactCompilerLoader to be consolidated into ReactCompilerPlugin
1 parent 986c815 commit 7d421b8

File tree

3 files changed

+225
-163
lines changed

3 files changed

+225
-163
lines changed

development/webpack/utils/loaders/reactCompilerLoader.ts

Lines changed: 0 additions & 144 deletions
This file was deleted.

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

Lines changed: 216 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,221 @@
1-
import type { Compiler } from 'webpack';
2-
import { getReactCompilerLogger } from '../../loaders/reactCompilerLoader';
1+
import type { Compiler, RuleSetRule, RuleSetCondition } from 'webpack';
2+
import {
3+
type ReactCompilerLoaderOption,
4+
defineReactCompilerLoaderOption,
5+
reactCompilerLoader,
6+
} from 'react-compiler-webpack';
7+
import type { Logger } from 'babel-plugin-react-compiler';
38

9+
/**
10+
* React Compiler logger that tracks compilation statistics.
11+
*/
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+
}
105+
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+
139+
/**
140+
* Webpack plugin that integrates React Compiler with logging and path filtering.
141+
*
142+
* @example
143+
* ```ts
144+
* const reactCompilerPlugin = new ReactCompilerPlugin({
145+
* target: '17',
146+
* verbose: true,
147+
* debug: 'critical',
148+
* include: /ui\/components/,
149+
* exclude: /\.test\./,
150+
* });
151+
*
152+
* // Add to webpack config
153+
* plugins: [reactCompilerPlugin],
154+
* module: {
155+
* rules: [reactCompilerPlugin.getLoaderRule()],
156+
* },
157+
* ```
158+
*/
4159
export class ReactCompilerPlugin {
160+
private readonly options: Required<ReactCompilerPluginOptions>;
161+
162+
private readonly logger: ReactCompilerLogger;
163+
164+
static readonly defaultTest =
165+
/(?:.(?!\.(?:test|stories|container)))+\.(?:m?[jt]s|[jt]sx)$/u;
166+
167+
constructor(options: ReactCompilerPluginOptions) {
168+
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,
175+
};
176+
this.logger = new ReactCompilerLogger();
177+
}
178+
179+
/**
180+
* Returns the webpack loader configuration for the React Compiler.
181+
* Use this in your module.rules array.
182+
*/
183+
getLoaderRule(): RuleSetRule {
184+
const { target, verbose, debug, include, exclude, test } = this.options;
185+
186+
const reactCompilerOptions = {
187+
target,
188+
logger: verbose ? (this.logger as Logger) : undefined,
189+
panicThreshold: debug === 'none' ? debug : `${debug}_errors`,
190+
} as const satisfies ReactCompilerLoaderOption;
191+
192+
return {
193+
test,
194+
include,
195+
exclude,
196+
use: [
197+
{
198+
loader: reactCompilerLoader,
199+
options: defineReactCompilerLoaderOption(reactCompilerOptions),
200+
},
201+
],
202+
};
203+
}
204+
205+
/**
206+
* Get compilation statistics from the logger.
207+
*/
208+
getStats() {
209+
return this.logger.getStats();
210+
}
211+
212+
/**
213+
* Reset the logger statistics.
214+
*/
215+
resetStats() {
216+
this.logger.reset();
217+
}
218+
5219
apply(compiler: Compiler): void {
6220
compiler.hooks.afterEmit.tap(ReactCompilerPlugin.name, () => {
7221
const logger = getReactCompilerLogger();

0 commit comments

Comments
 (0)