Skip to content

Commit

Permalink
fix: handle using compiler with React 18 (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnaudBarre authored Jun 10, 2024
1 parent 2737d41 commit ea647d1
Show file tree
Hide file tree
Showing 17 changed files with 342 additions and 3 deletions.
8 changes: 8 additions & 0 deletions packages/plugin-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

### Fix support for React Compiler with React 18

The previous version made this assumption that the compiler was only usable with React 19, but it's possible to use it with React 18 and a custom `runtimeModule`: https://gist.github.com/poteto/37c076bf112a07ba39d0e5f0645fec43

When using a custom `runtimeModule`, the plugin will not try to pre-optimize `react/compiler-runtime` dependency.

Reminder: Vite expect code outside of `node_modules` to be ESM, so you will need to update the gist with `import React from 'react'`.

## 4.3.0 (2024-05-22)

### Fix support for React compiler
Expand Down
13 changes: 12 additions & 1 deletion packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
const dependencies = ['react', jsxImportDevRuntime, jsxImportRuntime]
const staticBabelPlugins =
typeof opts.babel === 'object' ? opts.babel?.plugins ?? [] : []
if (hasCompiler(staticBabelPlugins)) {
if (hasCompilerWithDefaultRuntime(staticBabelPlugins)) {
dependencies.push('react/compiler-runtime')
}

Expand Down Expand Up @@ -376,3 +376,14 @@ function hasCompiler(plugins: ReactBabelOptions['plugins']) {
(Array.isArray(p) && p[0] === 'babel-plugin-react-compiler'),
)
}

// https://gist.github.com/poteto/37c076bf112a07ba39d0e5f0645fec43
function hasCompilerWithDefaultRuntime(plugins: ReactBabelOptions['plugins']) {
return plugins.some(
(p) =>
p === 'babel-plugin-react-compiler' ||
(Array.isArray(p) &&
p[0] === 'babel-plugin-react-compiler' &&
p[1]?.runtimeModule === undefined),
)
}
15 changes: 15 additions & 0 deletions playground/compiler-react-18/__tests__/compiler-react-18.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-react-18/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>
20 changes: 20 additions & 0 deletions playground/compiler-react-18/lib/react-compiler-runtime/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
const $empty = Symbol.for('react.memo_cache_sentinel')
/**
* DANGER: this hook is NEVER meant to be called directly!
*
* Note that this is a temporary userspace implementation of this function
* from React 19. It is not as efficient and may invalidate more frequently
* than the official API. Please upgrade to React 19 as soon as you can.
**/
export function c(size) {
return React.useState(() => {
const $ = new Array(size)
for (let ii = 0; ii < size; ii++) {
$[ii] = $empty
}
// @ts-ignore
$[$empty] = true
return $
})[0]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "react-compiler-runtime",
"private": true,
"main": "index.js",
"dependencies": {
"react": "^18.3.1"
}
}
24 changes: 24 additions & 0 deletions playground/compiler-react-18/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@vitejs/test-compiler-react-18",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-compiler-runtime": "file:./lib/react-compiler-runtime",
"react-dom": "^18.3.1"
},
"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-react-18/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-react-18/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-react-18/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 18 + compiler</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-react-18/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-react-18/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-react-18/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
}
}
23 changes: 23 additions & 0 deletions playground/compiler-react-18/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://gist.github.com/poteto/37c076bf112a07ba39d0e5f0645fec43

// https://vitejs.dev/config/
export default defineConfig(({ command }) => {
return {
server: { port: 8901 /* Should be unique */ },
plugins: [
react({
babel: {
plugins: [
[
'babel-plugin-react-compiler',
{ runtimeModule: 'react-compiler-runtime' },
],
],
},
}),
],
}
})
2 changes: 1 addition & 1 deletion playground/compiler/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@vitejs/compiler",
"name": "@vitejs/test-compiler",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion playground/compiler/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function App() {

return (
<>
<h1>Vite + React</h1>
<h1>Vite + React Compiler</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
Expand Down
Loading

0 comments on commit ea647d1

Please sign in to comment.