Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/create-vite/__tests__/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,27 @@ test('successfully scaffolds a project with subfolder based on react starter tem
expect(templateFilesReact).toEqual(generatedFiles)
})

test('successfully scaffolds a project based on react-compiler-ts starter template', () => {
const { stdout } = run([projectName, '--template', 'react-compiler-ts'], {
cwd: __dirname,
})
const configFile = fs.readFileSync(
path.join(genPath, 'vite.config.ts'),
'utf-8',
)
const packageJsonFile = fs.readFileSync(
path.join(genPath, 'package.json'),
'utf-8',
)
const readmeFile = fs.readFileSync(path.join(genPath, 'README.md'), 'utf-8')

// Assertions
expect(stdout).toContain(`Scaffolding project in ${genPath}`)
expect(configFile).toContain('babel-plugin-react-compiler')
expect(packageJsonFile).toContain('babel-plugin-react-compiler')
expect(readmeFile).toContain('The React Compiler is enabled on this template')
})

test('successfully scaffolds a project with subfolder based on react starter template with rolldown flag', () => {
const { stdout } = run(
[`subfolder/${projectName}`, '--template', 'react', '--rolldown'],
Expand Down
92 changes: 83 additions & 9 deletions packages/create-vite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,16 @@ Options:
--interactive / --no-interactive force interactive / non-interactive mode

Available templates:
${yellow ('vanilla-ts vanilla' )}
${green ('vue-ts vue' )}
${cyan ('react-ts react' )}
${cyan ('react-swc-ts react-swc')}
${magenta ('preact-ts preact' )}
${redBright ('lit-ts lit' )}
${red ('svelte-ts svelte' )}
${blue ('solid-ts solid' )}
${blueBright('qwik-ts qwik' )}`
${yellow ('vanilla-ts vanilla' )}
${green ('vue-ts vue' )}
${cyan ('react-ts react' )}
${cyan ('react-compiler-ts react-compiler')}
${cyan ('react-swc-ts react-swc' )}
${magenta ('preact-ts preact' )}
${redBright ('lit-ts lit' )}
${red ('svelte-ts svelte' )}
${blue ('solid-ts solid' )}
${blueBright('qwik-ts qwik' )}`

type ColorFunc = (str: string | number) => string
type Framework = {
Expand Down Expand Up @@ -128,6 +129,11 @@ const FRAMEWORKS: Framework[] = [
display: 'TypeScript',
color: blue,
},
{
name: 'react-compiler-ts',
display: 'TypeScript + React Compiler',
color: blue,
},
{
name: 'react-swc-ts',
display: 'TypeScript + SWC',
Expand All @@ -138,6 +144,11 @@ const FRAMEWORKS: Framework[] = [
display: 'JavaScript',
color: yellow,
},
{
name: 'react-compiler',
display: 'JavaScript + React Compiler',
color: yellow,
},
{
name: 'react-swc',
display: 'JavaScript + SWC',
Expand Down Expand Up @@ -566,6 +577,11 @@ async function init() {
isReactSwc = true
template = template.replace('-swc', '')
}
let isReactCompiler = false
if (template.includes('react-compiler')) {
isReactCompiler = true
template = template.replace('-compiler', '')
}

const { customCommand } =
FRAMEWORKS.flatMap((f) => f.variants).find((v) => v.name === template) ?? {}
Expand Down Expand Up @@ -667,6 +683,8 @@ async function init() {

if (isReactSwc) {
setupReactSwc(root, template.endsWith('-ts'))
} else if (isReactCompiler) {
setupReactCompiler(root, template.endsWith('-ts'))
}

if (immediate) {
Expand Down Expand Up @@ -780,6 +798,62 @@ function setupReactSwc(root: string, isTs: boolean) {
return content.replace('@vitejs/plugin-react', '@vitejs/plugin-react-swc')
},
)
updateReactCompilerReadme(
root,
'The React Compiler is currently not compatible with SWC. See [this issue](https://github.com/vitejs/vite-plugin-react/issues/428) for tracking the progress.',
)
}

function setupReactCompiler(root: string, isTs: boolean) {
// renovate: datasource=npm depName=babel-plugin-react-compiler
const reactCompilerPluginVersion = '19.1.0-rc.3'

editFile(path.resolve(root, 'package.json'), (content) => {
const asObject = JSON.parse(content)
const devDepsEntries = Object.entries(asObject.devDependencies)
devDepsEntries.push([
'babel-plugin-react-compiler',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one thing that's missing is also using [email protected]. Version 6 will include compiler diagnostics as part of the plugin by default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's quite a bump in dependencies. plugin-proposal-private-methods is deprecated, and I'm surprise of the need for zod in a linter plugin.
Is the hermes parser necessary given that the build will use the babel parser?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not as much an issue, but are you open to switching to ESM for the next major? ESLint v9 is now quite the norm and fully support ESM. I can make a PR to either reduce the dependency or switch to ESM if you think there is a chance it lands

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArnaudBarre yes, the eslint plugin now bundles the compiler as well since it can be used standalone. That means a few extra dependencies are needed. Is that blocking anything?

As for ESM I think that will require a larger discussion with the rest of the React team.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The react compiler only requires @babel/types as a dependency so I'm still unsure what are the need for all these extra dependencies. It adds extra install size (zod 3 & hermes-parser probably not be present for a number of apps) and extra attack surface for package hijacking like what happen yesterday.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArnaudBarre we inline most dependencies in babel-plugin-react-compiler which is why it only appears to depend on @babel/types (this is mostly a consequence of some Meta-internal infra quirks). For eslint-plugin-react-hooks we didn't, but I can inline as well if it's a hard requirement to get this shipped. Effectively the eslint plugin now includes the entire compiler as it needs to be able to be used standalone, hence the zod (validating compiler configs) and hermes-parser (parsing both Flow and JavaScript/TypeScript into a Babel compatible AST) dependencies.

Copy link
Member Author

@ArnaudBarre ArnaudBarre Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's needed to depend on it, it's not directly used: https://github.com/search?q=repo%3Afacebook%2Freact%20path%3A%2F%5Epackages%5C%2Feslint-plugin-react-hooks%5C%2F%2F%20zod&type=code

Instead I think the eslint plugin should make babel-plugin-react-compiler a peer-dependency? (maybe optional if it's not required for some rules and lazy loaded for others)

Edit: Ok I see that the plugin rebundle the compiler, why not depend on it (to avoid having it twice). It would also make the runtime & lint rules in sync for users?

Also the current published version as two 2MB files, is it possible to only publish the development version?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@poteto is there somewhere we could discuss this stuff outside of this PR too?

the modern approach to custom parsers in ESLint is to provide a language implementation, like how @eslint/json works. the hooks plugin shouldn't really need to care about parsing at all, since a language should've already been setup to parse the various formats correctly (in this case, flow and a custom JS parse with the compiler plugin).

this is a fairly modern part of ESLint though so makes sense the hooks plugin doesn't do it this way yet. so maybe we should track it in an issue somewhere?

`^${reactCompilerPluginVersion}`,
])
devDepsEntries.sort()
asObject.devDependencies = Object.fromEntries(devDepsEntries)
return JSON.stringify(asObject, null, 2) + '\n'
})
editFile(
path.resolve(root, `vite.config.${isTs ? 'ts' : 'js'}`),
(content) => {
return content.replace(
' plugins: [react()],',
` plugins: [
react({
babel: {
plugins: [['babel-plugin-react-compiler']],
},
}),
],`,
)
},
)
updateReactCompilerReadme(
root,
'The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.\n\nNote: This will impact Vite dev & build performances.',
)
}

function updateReactCompilerReadme(root: string, newBody: string) {
editFile(path.resolve(root, `README.md`), (content) => {
const h2Start = content.indexOf('## React Compiler')
const bodyStart = content.indexOf('\n\n', h2Start)
const compilerSectionEnd = content.indexOf('\n## ', bodyStart)
if (h2Start === -1 || bodyStart === -1 || compilerSectionEnd === -1) {
console.warn('Could not update compiler section in README.md')
return content
}
return content.replace(
content.slice(bodyStart + 2, compilerSectionEnd - 1),
newBody,
)
})
}

function editFile(file: string, callback: (content: string) => string) {
Expand Down
4 changes: 4 additions & 0 deletions packages/create-vite/template-react-ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
Expand Down
4 changes: 4 additions & 0 deletions packages/create-vite/template-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## React Compiler

The React Compiler is not enabled on this template. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).

## Expanding the ESLint configuration

If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
Loading