Skip to content

Commit

Permalink
fix: don't use retainLines with react compiler (#319)
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnaudBarre authored May 22, 2024
1 parent 2b7f2ae commit 4b4a95c
Show file tree
Hide file tree
Showing 24 changed files with 478 additions and 9 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
hoist-pattern[]=@emotion/* # playground/react-emotion
hoist-pattern[]=*babel*
strict-peer-dependencies=false
shell-emulator=true
auto-install-peers=false
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"ci-publish": "tsx scripts/publishCI.ts"
},
"devDependencies": {
"@eslint-types/typescript-eslint": "^7.5.0",
"@eslint-types/import": "^2.29.1",
"@eslint-types/typescript-eslint": "^7.5.0",
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.12.12",
"@typescript-eslint/eslint-plugin": "^7.9.0",
Expand Down
17 changes: 17 additions & 0 deletions packages/plugin-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

## Unreleased

### Fix support for React compiler

Don't set `retainLines: true` when the React compiler is used. This creates whitespace issues and the compiler is modifying the JSX too much to get correct line numbers after that. If you want to use the React compiler and get back correct line numbers for tools like [vite-plugin-react-click-to-component](https://github.com/ArnaudBarre/vite-plugin-react-click-to-component) to work, you should update your config to something like:

```ts
export default defineConfig(({ command }) => {
const babelPlugins = [['babel-plugin-react-compiler', {}]]
if (command === 'serve') {
babelPlugins.push(['@babel/plugin-transform-react-jsx-development', {}])
}

return {
plugins: [react({ babel: { plugins: babelPlugins } })],
}
})
```

### Support HMR for class components

This is a long overdue and should fix some issues people had with HMR when migrating from CRA.
Expand Down
29 changes: 24 additions & 5 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,11 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
filename: id,
sourceFileName: filepath,
// Required for esbuild.jsxDev to provide correct line numbers
retainLines: !isProduction && isJSX && opts.jsxRuntime !== 'classic',
// This crates issues the react compiler because the re-order is too important
// People should use @babel/plugin-transform-react-jsx-development to get back good line numbers
retainLines: hasCompiler(plugins)
? false
: !isProduction && isJSX && opts.jsxRuntime !== 'classic',
parserOpts: {
...babelOptions.parserOpts,
sourceType: 'module',
Expand Down Expand Up @@ -264,16 +268,23 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
},
}

// We can't add `react-dom` because the dependency is `react-dom/client`
// for React 18 while it's `react-dom` for React 17. We'd need to detect
// what React version the user has installed.
const dependencies = ['react', jsxImportDevRuntime, jsxImportRuntime]
const staticBabelPlugins =
typeof opts.babel === 'object' ? opts.babel?.plugins ?? [] : []
if (hasCompiler(staticBabelPlugins)) {
dependencies.push('react/compiler-runtime')
}

const viteReactRefresh: Plugin = {
name: 'vite:react-refresh',
enforce: 'pre',
config: (userConfig) => ({
build: silenceUseClientWarning(userConfig),
optimizeDeps: {
// We can't add `react-dom` because the dependency is `react-dom/client`
// for React 18 while it's `react-dom` for React 17. We'd need to detect
// what React version the user has installed.
include: ['react', jsxImportDevRuntime, jsxImportRuntime],
include: dependencies,
},
resolve: {
dedupe: ['react', 'react-dom'],
Expand Down Expand Up @@ -357,3 +368,11 @@ function createBabelOptions(rawOptions?: BabelOptions) {
function defined<T>(value: T | undefined): value is T {
return value !== undefined
}

function hasCompiler(plugins: ReactBabelOptions['plugins']) {
return plugins.some(
(p) =>
p === 'babel-plugin-react-compiler' ||
(Array.isArray(p) && p[0] === 'babel-plugin-react-compiler'),
)
}
15 changes: 15 additions & 0 deletions playground/compiler/__tests__/compiler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect, test } from 'vitest'
import { editFile, isServe, page, untilUpdated } from '~utils'

test('should render', async () => {
expect(await page.textContent('button')).toMatch('count is 0')
expect(await page.click('button'))
expect(await page.textContent('button')).toMatch('count is 1')
})

test.runIf(isServe)('should hmr', async () => {
editFile('src/App.tsx', (code) =>
code.replace('count is {count}', 'count is {count}!'),
)
await untilUpdated(() => page.textContent('button'), 'count is 1!')
})
13 changes: 13 additions & 0 deletions playground/compiler/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
23 changes: 23 additions & 0 deletions playground/compiler/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@vitejs/compiler",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0-rc-3f1436cca1-20240516",
"react-dom": "^19.0.0-rc-3f1436cca1-20240516"
},
"devDependencies": {
"@babel/plugin-transform-react-jsx-development": "^7.22.5",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "workspace:*",
"babel-plugin-react-compiler": "^0.0.0-experimental-592953e-20240517",
"typescript": "^5.4.5",
"vite": "^5.2.11"
}
}
1 change: 1 addition & 0 deletions playground/compiler/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions playground/compiler/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
23 changes: 23 additions & 0 deletions playground/compiler/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useState } from 'react'
import './App.css'

export function App() {
const [count, setCount] = useState(0)

return (
<>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}
69 changes: 69 additions & 0 deletions playground/compiler/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}

a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
}

button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
10 changes: 10 additions & 0 deletions playground/compiler/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { App } from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
25 changes: 25 additions & 0 deletions playground/compiler/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"include": ["src"],
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
15 changes: 15 additions & 0 deletions playground/compiler/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig(({ command }) => {
const babelPlugins = [['babel-plugin-react-compiler', {}]]
if (command === 'serve') {
babelPlugins.push(['@babel/plugin-transform-react-jsx-development', {}])
}

return {
server: { port: 8900 /* Should be unique */ },
plugins: [react({ babel: { plugins: babelPlugins } })],
}
})
1 change: 1 addition & 0 deletions playground/mdx/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import mdx from '@mdx-js/rollup'

// https://vitejs.dev/config/
export default defineConfig({
server: { port: 8901 /* Should be unique */ },
plugins: [
{ enforce: 'pre', ...mdx() },
react({ include: /\.(mdx|md|ts|tsx)$/ }),
Expand Down
1 change: 1 addition & 0 deletions playground/react-classic/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import react from '@vitejs/plugin-react'
import type { UserConfig } from 'vite'

const config: UserConfig = {
server: { port: 8903 /* Should be unique */ },
plugins: [
react({
jsxRuntime: 'classic',
Expand Down
1 change: 1 addition & 0 deletions playground/react-emotion/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

export default defineConfig({
server: { port: 8904 /* Should be unique */ },
plugins: [
react({
jsxImportSource: '@emotion/react',
Expand Down
1 change: 1 addition & 0 deletions playground/react-env/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { UserConfig } from 'vite'
process.env.NODE_ENV = ''

const config: UserConfig = {
server: { port: 8905 /* Should be unique */ },
plugins: [react()],
mode: 'staging',
build: {
Expand Down
1 change: 1 addition & 0 deletions playground/react-sourcemap/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import react from '@vitejs/plugin-react'
import type { UserConfig } from 'vite'

const config: UserConfig = {
server: { port: 8906 /* Should be unique */ },
plugins: [
react({
jsxRuntime: process.env.USE_CLASSIC === '1' ? 'classic' : 'automatic',
Expand Down
1 change: 1 addition & 0 deletions playground/react/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import react from '@vitejs/plugin-react'
import type { UserConfig } from 'vite'

const config: UserConfig = {
server: { port: 8902 /* Should be unique */ },
mode: 'development',
plugins: [react()],
build: {
Expand Down
1 change: 1 addition & 0 deletions playground/ssr-react/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
server: { port: 8907 /* Should be unique */ },
plugins: [react()],
build: {
minify: false,
Expand Down
Loading

0 comments on commit 4b4a95c

Please sign in to comment.