Skip to content

Commit 552af8f

Browse files
authored
feat(react): add raw rolldown support (#513)
1 parent 18e89ce commit 552af8f

File tree

7 files changed

+254
-23
lines changed

7 files changed

+254
-23
lines changed

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export default tseslint.config(
127127
{
128128
name: 'disables/playground',
129129
files: [
130+
'packages/**/*.test.?([cm])[jt]s?(x)',
130131
'playground/**/*.?([cm])[jt]s?(x)',
131132
'packages/plugin-react-swc/playground/**/*.?([cm])[jt]s?(x)',
132133
],

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"format": "prettier --write --cache .",
2222
"lint": "eslint --cache .",
2323
"typecheck": "tsc -p scripts && tsc -p playground && tsc -p packages/plugin-react",
24-
"test": "pnpm run test-serve && pnpm run test-build && pnpm --filter ./packages/plugin-react-swc run test",
24+
"test": "pnpm run test-unit && pnpm run test-serve && pnpm run test-build && pnpm --filter ./packages/plugin-react-swc run test",
25+
"test-unit": "pnpm -r --filter='./packages/*' run test-unit",
2526
"test-serve": "vitest run -c playground/vitest.config.e2e.ts",
2627
"test-build": "VITE_TEST_BUILD=1 vitest run -c playground/vitest.config.e2e.ts",
2728
"debug-serve": "VITE_DEBUG_SERVE=1 vitest run -c playground/vitest.config.e2e.ts",

packages/plugin-react/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Add raw Rolldown support
6+
7+
This plugin only worked with Vite. But now it can also be used with raw Rolldown. The main purpose for using this plugin with Rolldown is to use react compiler.
8+
59
## 4.5.2 (2025-06-10)
610

711
### Suggest `@vitejs/plugin-react-oxc` if rolldown-vite is detected [#491](https://github.com/vitejs/vite-plugin-react/pull/491)

packages/plugin-react/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"dev": "unbuild --stub",
3434
"build": "unbuild && pnpm run patch-cjs && tsx scripts/copyRefreshRuntime.ts",
3535
"patch-cjs": "tsx ../../scripts/patchCJS.ts",
36-
"prepublishOnly": "npm run build"
36+
"prepublishOnly": "npm run build",
37+
"test-unit": "vitest run"
3738
},
3839
"engines": {
3940
"node": "^14.18.0 || >=16.0.0"
@@ -60,6 +61,11 @@
6061
},
6162
"devDependencies": {
6263
"@vitejs/react-common": "workspace:*",
63-
"unbuild": "^3.5.0"
64+
"babel-plugin-react-compiler": "19.1.0-rc.2",
65+
"react": "^19.1.0",
66+
"react-dom": "^19.1.0",
67+
"rolldown": "1.0.0-beta.17",
68+
"unbuild": "^3.5.0",
69+
"vitest": "^3.2.3"
6470
}
6571
}

packages/plugin-react/src/index.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ const _dirname = dirname(fileURLToPath(import.meta.url))
2222

2323
const refreshRuntimePath = globalThis.__IS_BUILD__
2424
? join(_dirname, 'refresh-runtime.js')
25-
: // eslint-disable-next-line n/no-unsupported-features/node-builtins -- only used in dev
26-
fileURLToPath(import.meta.resolve('@vitejs/react-common/refresh-runtime'))
25+
: join(_dirname, '../../common/refresh-runtime.js')
2726

2827
// lazy load babel since it's not used during build if plugins are not used
2928
let babel: typeof babelCore | undefined
@@ -118,9 +117,10 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
118117
const jsxImportSource = opts.jsxImportSource ?? 'react'
119118
const jsxImportRuntime = `${jsxImportSource}/jsx-runtime`
120119
const jsxImportDevRuntime = `${jsxImportSource}/jsx-dev-runtime`
120+
let runningInVite = false
121121
let isProduction = true
122122
let projectRoot = process.cwd()
123-
let skipFastRefresh = false
123+
let skipFastRefresh = true
124124
let runPluginOverrides:
125125
| ((options: ReactBabelOptions, context: ReactBabelHookContext) => void)
126126
| undefined
@@ -170,6 +170,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
170170
}
171171
},
172172
configResolved(config) {
173+
runningInVite = true
173174
projectRoot = config.root
174175
isProduction = config.isProduction
175176
skipFastRefresh =
@@ -217,6 +218,15 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
217218
}
218219
}
219220
},
221+
options(options) {
222+
if (!runningInVite) {
223+
options.jsx = {
224+
mode: opts.jsxRuntime,
225+
importSource: opts.jsxImportSource,
226+
}
227+
return options
228+
}
229+
},
220230
transform: {
221231
filter: {
222232
id: {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import path from 'node:path'
2+
import { expect, test } from 'vitest'
3+
import { type Plugin, rolldown } from 'rolldown'
4+
import pluginReact, { type Options } from '../src/index.ts'
5+
6+
test('HMR related code should not be included when using rolldown', async () => {
7+
const { output } = await bundleWithRolldown()
8+
9+
expect(output[0].code).toBeDefined()
10+
expect(output[0].code).not.toContain('import.meta.hot')
11+
})
12+
13+
test('HMR related code should not be included when using rolldown with babel plugin', async () => {
14+
const { output } = await bundleWithRolldown({
15+
babel: {
16+
plugins: [['babel-plugin-react-compiler', {}]],
17+
},
18+
})
19+
20+
expect(output[0].code).toBeDefined()
21+
expect(output[0].code).not.toContain('import.meta.hot')
22+
})
23+
24+
async function bundleWithRolldown(pluginReactOptions: Options = {}) {
25+
const ENTRY = '/entry.tsx'
26+
const files: Record<string, string> = {
27+
[ENTRY]: /* tsx */ `
28+
import React from "react"
29+
import { hydrateRoot } from "react-dom/client"
30+
import App from "./App.tsx"
31+
32+
const container = document.getElementById("root");
33+
hydrateRoot(container, <App />);
34+
`,
35+
'/App.tsx': /* tsx */ `
36+
export default function App() {
37+
return <div>Hello World</div>
38+
}
39+
`,
40+
}
41+
42+
const bundle = await rolldown({
43+
input: ENTRY,
44+
plugins: [virtualFilesPlugin(files), pluginReact(pluginReactOptions)],
45+
external: [/^react(\/|$)/, /^react-dom(\/|$)/],
46+
})
47+
return await bundle.generate({ format: 'esm' })
48+
}
49+
50+
function virtualFilesPlugin(files: Record<string, string>): Plugin {
51+
return {
52+
name: 'virtual-files',
53+
resolveId(id, importer) {
54+
const baseDir = importer ? path.posix.dirname(importer) : '/'
55+
const result = path.posix.resolve(baseDir, id)
56+
if (result in files) {
57+
return result
58+
}
59+
},
60+
load(id) {
61+
if (id in files) {
62+
return files[id]
63+
}
64+
},
65+
}
66+
}

0 commit comments

Comments
 (0)