Skip to content

Commit 82ecfd4

Browse files
authored
feat: Drop requirement to have a user-provided babel config (#15)
* feat: Drop requirement to have a user-provided babel config * Add hooks to integration test * Silence expected error in records file test
1 parent 7f3f144 commit 82ecfd4

11 files changed

Lines changed: 167 additions & 237 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ Designed to run as a part of Git hooks and CI, this tool helps prevent this and
66

77
## Prerequisites
88

9-
This tool requires the React Compiler to be configured in your project using `babel.config.js`.
9+
This tool requires `babel-plugin-react-compiler` to be installed in your project:
1010

11-
See the [React Compiler installation guide](https://react.dev/learn/react-compiler/installation#babel) for setup instructions.
11+
```bash
12+
npm install --save-dev babel-plugin-react-compiler
13+
```
1214

1315
## Installation
1416

package-lock.json

Lines changed: 113 additions & 170 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@
4646
"*.{js,ts,mjs,mts,json,md}": "biome check --write --no-errors-on-unmatched"
4747
},
4848
"devDependencies": {
49-
"@babel/core": "7.28.5",
50-
"@babel/preset-react": "7.28.5",
51-
"@babel/preset-typescript": "7.28.5",
5249
"@biomejs/biome": "2.3.11",
5350
"@types/babel__core": "7.20.5",
5451
"@types/node": "25.0.6",
@@ -61,10 +58,12 @@
6158
"vitest": "4.0.16"
6259
},
6360
"peerDependencies": {
64-
"@babel/core": "^7.0.0",
6561
"babel-plugin-react-compiler": "^1.0.0"
6662
},
6763
"dependencies": {
64+
"@babel/core": "7.28.6",
65+
"@babel/preset-react": "7.28.5",
66+
"@babel/preset-typescript": "7.28.5",
6867
"glob": "13.0.0"
6968
}
7069
}

src/__fixtures__/sample-project/src/with-violation.tsx renamed to src/__fixtures__/sample-project/src/bad-component.tsx

File renamed without changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useRef } from 'react'
2+
3+
/**
4+
* A custom hook with a React Compiler violation (mutating ref during render)
5+
*/
6+
export function useBadCounter() {
7+
const countRef = useRef(0)
8+
// Mutating ref during render - React Compiler violation
9+
countRef.current += 1
10+
return countRef.current
11+
}
File renamed without changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useCallback, useState } from 'react'
2+
3+
/**
4+
* A clean custom hook that compiles without issues
5+
*/
6+
export function useCounter(initialValue: number = 0) {
7+
const [count, setCount] = useState(initialValue)
8+
const increment = useCallback(() => setCount((c) => c + 1), [])
9+
const decrement = useCallback(() => setCount((c) => c - 1), [])
10+
return { count, increment, decrement }
11+
}

src/babel.mts

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,11 @@
1-
import { createRequire } from 'node:module'
2-
import { join } from 'node:path'
3-
import {
4-
type ConfigAPI,
5-
type ConfigFunction,
6-
type TransformOptions,
7-
transformFileAsync,
8-
} from '@babel/core'
1+
import { type TransformOptions, transformFileAsync } from '@babel/core'
92
import type { Logger } from 'babel-plugin-react-compiler'
103

11-
function loadConfig(configPath: string) {
12-
const require = createRequire(import.meta.url)
13-
const babelConfigPath = join(process.cwd(), configPath)
14-
const babelConfigFn: ConfigFunction = require(babelConfigPath)
15-
const babelConfig = babelConfigFn({
16-
cache: {
17-
using: (callback) => callback(),
18-
},
19-
} as ConfigAPI)
20-
21-
return babelConfig
22-
}
23-
24-
function setCustomReactCompilerLogger(
25-
babelConfig: TransformOptions,
26-
customReactCompilerLogger: Logger,
27-
) {
28-
if (!babelConfig?.plugins) {
29-
throw new Error('Failed to load Babel config')
30-
}
31-
32-
const reactCompilerPlugin = babelConfig.plugins.find(
33-
(plugin) => Array.isArray(plugin) && plugin[0] === 'babel-plugin-react-compiler',
34-
)
35-
if (!reactCompilerPlugin || !Array.isArray(reactCompilerPlugin)) {
36-
throw new Error('Failed to find React Compiler plugin in Babel config')
4+
function createConfig(logger: Logger): TransformOptions {
5+
return {
6+
presets: [['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'],
7+
plugins: [['babel-plugin-react-compiler', { logger }]],
378
}
38-
reactCompilerPlugin[1] = { ...reactCompilerPlugin[1], logger: customReactCompilerLogger }
399
}
4010

4111
async function compileFileWithBabel(filePath: string, config: TransformOptions) {
@@ -49,23 +19,13 @@ async function compileFileWithBabel(filePath: string, config: TransformOptions)
4919

5020
async function compileFiles({
5121
filePaths,
52-
configPath,
5322
customReactCompilerLogger,
5423
}: {
5524
filePaths: string[]
56-
configPath: string
5725
customReactCompilerLogger: Logger
5826
}) {
59-
const config = loadConfig(configPath)
60-
setCustomReactCompilerLogger(config, customReactCompilerLogger)
61-
62-
const processJobs: Promise<void>[] = []
63-
64-
for (const filePath of filePaths) {
65-
processJobs.push(compileFileWithBabel(filePath, config))
66-
}
67-
68-
await Promise.all(processJobs)
27+
const config = createConfig(customReactCompilerLogger)
28+
await Promise.all(filePaths.map((filePath) => compileFileWithBabel(filePath, config)))
6929
}
7030

7131
export { compileFiles }

src/index.integration.test.mts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,39 +32,44 @@ describe('CLI', () => {
3232
it('runs check on all files when no flag provided', () => {
3333
const output = runCLI()
3434

35-
expect(output).toContain('🔍 Checking all 2 source files for React Compiler errors…')
36-
expect(output).toContain('⚠️ Found 1 React Compiler issues across 1 files')
35+
expect(output).toContain('🔍 Checking all 4 source files for React Compiler errors…')
36+
expect(output).toContain('⚠️ Found 4 React Compiler issues across 2 files')
3737
expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow()
3838
})
3939

4040
it('accepts --check-files flag with file arguments', () => {
41-
const output = runCLI(['--check-files', 'src/with-violation.tsx'])
41+
const output = runCLI(['--check-files', 'src/bad-component.tsx', 'src/bad-hook.ts'])
4242

43-
expect(output).toContain('🔍 Checking 1 files for React Compiler errors…')
43+
expect(output).toContain('🔍 Checking 2 files for React Compiler errors…')
4444
expect(output).toContain('React Compiler errors have increased in:')
45-
expect(output).toContain('• src/with-violation.tsx: +1')
45+
expect(output).toContain('• src/bad-component.tsx: +1')
46+
expect(output).toContain('• src/bad-hook.ts: +3')
4647
expect(output).toContain('Please fix the errors and run the command again.')
47-
expect(output).not.toContain('src/clean.tsx')
48+
expect(output).not.toContain('src/good-component.tsx')
49+
expect(output).not.toContain('src/good-hook.ts')
4850
expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow()
4951
})
5052

5153
it('accepts --overwrite flag', () => {
5254
const output = runCLI(['--overwrite'])
5355

5456
expect(output).toContain(
55-
'🔍 Checking all 2 source files for React Compiler errors and recreating records…',
57+
'🔍 Checking all 4 source files for React Compiler errors and recreating records…',
5658
)
5759
expect(output).toContain(
58-
'✅ Records file completed. Found 1 total React Compiler issues across 1 files',
60+
'✅ Records file completed. Found 4 total React Compiler issues across 2 files',
5961
)
6062

6163
const records = JSON.parse(readFileSync(recordsPath, 'utf8'))
6264
expect(records.recordVersion).toBe(1)
6365
expect(records['react-compiler-version']).toBe('1.0.0')
6466
expect(records.files).toEqual({
65-
'src/with-violation.tsx': {
67+
'src/bad-component.tsx': {
6668
CompileError: 1,
6769
},
70+
'src/bad-hook.ts': {
71+
CompileError: 3,
72+
},
6873
})
6974
})
7075
})

src/index.mts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import * as sourceFiles from './source-files.mjs'
1010
const RECORDS_PATH = '.react-compiler.rec.json'
1111
const SUPPORTED_FILE_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx']
1212
const SOURCE_FILES = 'src/**/*.{js,jsx,ts,tsx}'
13-
const BABEL_CONFIG_PATH = 'babel.config.js'
1413

1514
const OVERWRITE_FLAG = '--overwrite'
1615
const STAGE_RECORD_FILE_FLAG = '--stage-record-file'
@@ -91,7 +90,6 @@ async function runOverwriteRecords() {
9190

9291
await babel.compileFiles({
9392
filePaths,
94-
configPath: BABEL_CONFIG_PATH,
9593
customReactCompilerLogger: customReactCompilerLogger,
9694
})
9795

@@ -147,7 +145,6 @@ async function runStageRecords() {
147145

148146
await babel.compileFiles({
149147
filePaths,
150-
configPath: BABEL_CONFIG_PATH,
151148
customReactCompilerLogger: customReactCompilerLogger,
152149
})
153150

@@ -197,7 +194,6 @@ async function runCheckFiles(filePathArgs: string[]) {
197194

198195
await babel.compileFiles({
199196
filePaths: filePaths,
200-
configPath: BABEL_CONFIG_PATH,
201197
customReactCompilerLogger: customReactCompilerLogger,
202198
})
203199

@@ -229,7 +225,6 @@ async function runCheckAllFiles() {
229225

230226
await babel.compileFiles({
231227
filePaths,
232-
configPath: BABEL_CONFIG_PATH,
233228
customReactCompilerLogger: customReactCompilerLogger,
234229
})
235230

0 commit comments

Comments
 (0)