Skip to content

Commit e8c8bbe

Browse files
committed
fix(plugin-react): use '/' base in bundledDev preamble to fix non-root base paths
In bundled dev mode (`experimental.bundledDev: true`), Rolldown resolves module specifiers at build time without any URL-level base stripping. Using `config.base` in `getPreambleCode` produced an import like `/@react-refresh` prefixed with the custom base (e.g. `/static/@react-refresh`), which Rolldown couldn't resolve since the `resolveId` hook only matches the exact string `/@react-refresh`. Fix by passing `'/'` instead of `config.base` to `getPreambleCode` in the `viteReactRefreshBundledDevMode` plugin, consistent with how `virtualPreamblePlugin` already handles this case. Fixes #1190 Made-with: Cursor
1 parent ebd9b09 commit e8c8bbe

7 files changed

Lines changed: 101 additions & 1 deletion

File tree

packages/plugin-react/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ export default function viteReact(opts: Options = {}): Plugin[] {
172172
{
173173
tag: 'script',
174174
attrs: { type: 'module' },
175-
children: getPreambleCode(base),
175+
// In bundled dev mode, Rolldown resolves module specifiers at build
176+
// time without URL-level base stripping, so we must use '/' instead
177+
// of config.base to match the resolveId hook for '/@react-refresh'.
178+
children: getPreambleCode('/'),
176179
},
177180
]
178181
},
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useState } from 'react'
2+
3+
function App() {
4+
const [count, setCount] = useState(0)
5+
return (
6+
<div>
7+
<h1>bundledDev + base path</h1>
8+
<button id="state-button" onClick={() => setCount((c) => c + 1)}>
9+
count is: {count}
10+
</button>
11+
</div>
12+
)
13+
}
14+
15+
export default App
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { expect, test } from 'vitest'
2+
import { browserErrors, isServe, page, viteTestUrl } from '~utils'
3+
4+
// Regression test for https://github.com/vitejs/vite-plugin-react/issues/1190
5+
// bundledDev mode with a non-root base should not fail to resolve /@react-refresh.
6+
test.runIf(isServe)(
7+
'should load without UNRESOLVED_IMPORT error for @react-refresh',
8+
async () => {
9+
await page.goto(viteTestUrl)
10+
// bundledDev shows "Bundling in progress" until the first bundle is ready.
11+
await page.waitForSelector('h1')
12+
await expect
13+
.poll(() => page.textContent('h1'))
14+
.toMatch('bundledDev + base path')
15+
expect(browserErrors).toHaveLength(0)
16+
},
17+
)
18+
19+
test.runIf(isServe)('should render and update state', async () => {
20+
await page.goto(viteTestUrl)
21+
await page.waitForSelector('#state-button')
22+
await expect
23+
.poll(() => page.textContent('#state-button'))
24+
.toMatch('count is: 0')
25+
await page.click('#state-button')
26+
await expect
27+
.poll(() => page.textContent('#state-button'))
28+
.toMatch('count is: 1')
29+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div id="app"></div>
2+
<script type="module">
3+
import React from 'react'
4+
import ReactDOM from 'react-dom/client'
5+
import App from './App.jsx'
6+
7+
ReactDOM.createRoot(document.getElementById('app')).render(
8+
React.createElement(App),
9+
)
10+
</script>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@vitejs/test-bundled-dev-base-path",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build",
8+
"preview": "vite preview"
9+
},
10+
"dependencies": {
11+
"react": "^19.2.5",
12+
"react-dom": "^19.2.5"
13+
},
14+
"devDependencies": {
15+
"@vitejs/plugin-react": "workspace:*"
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import react from '@vitejs/plugin-react'
2+
import type { UserConfig } from 'vite'
3+
4+
const config: UserConfig = {
5+
server: { port: 8930 /* Should be unique */ },
6+
mode: 'development',
7+
base: '/static/',
8+
plugins: [react()],
9+
experimental: { bundledDev: true },
10+
build: { minify: false },
11+
}
12+
13+
export default config

pnpm-lock.yaml

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

0 commit comments

Comments
 (0)