Skip to content
Closed
3 changes: 3 additions & 0 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
".": "./index.ts",
"./refresh-runtime": "./refresh-runtime.js"
},
"dependencies": {
"@rolldown/pluginutils": "1.0.0-beta.41"
},
"peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/common/refresh-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ function collectCustomHooksForSignature(type) {
}

export function injectIntoGlobalHook(globalObject) {
if (globalObject.__vite_plugin_react_injectIntoGlobalHook) return
globalObject.__vite_plugin_react_injectIntoGlobalHook = true

// For React Native, the global hook will be set up by require('react-devtools-core').
// That code will run before us. So we need to monkeypatch functions on existing hook.

Expand Down
55 changes: 55 additions & 0 deletions packages/common/refresh-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { Plugin } from 'vite'
import { exactRegex } from '@rolldown/pluginutils'

export const runtimePublicPath = '/@react-refresh'

const reactCompRE = /extends\s+(?:React\.)?(?:Pure)?Component/
Expand Down Expand Up @@ -60,3 +63,55 @@ function $RefreshSig$() { return RefreshRuntime.createSignatureFunctionForTransf

return newCode
}

export function virtualPreamblePlugin(opts: {
isEnabled: () => boolean
reactRefreshHost?: string
}): Plugin {
const VIRTUAL_NAME = 'virtual:@vitejs/plugin-react/preamble'
let importSource = VIRTUAL_NAME
if (opts.reactRefreshHost) {
importSource = opts.reactRefreshHost + '/@id/__x00__' + VIRTUAL_NAME
}
return {
name: 'vite:react-virtual-preamble',
apply: 'serve',
resolveId: {
order: 'pre',
filter: { id: exactRegex(VIRTUAL_NAME) },
handler(source) {
if (source === VIRTUAL_NAME) {
return '\0' + source
}
},
},
load: {
filter: { id: exactRegex('\0' + VIRTUAL_NAME) },
handler(id) {
if (id === '\0' + VIRTUAL_NAME) {
if (opts.isEnabled()) {
// vite dev import analysis can rewrite base
return preambleCode.replace('__BASE__', '/')
}
return ''
}
},
},
transform: {
filter: { code: /__REACT_DEVTOOLS_GLOBAL_HOOK__/ },
handler(code, id, options) {
if (options?.ssr) return
if (id === runtimePublicPath) return

// this is expected to match `react`, `react-dom`, and `react-dom/client`.
// they are all optimized to be esm during dev.
if (
opts.isEnabled() &&
code.includes('__REACT_DEVTOOLS_GLOBAL_HOOK__')
) {
return `import ${JSON.stringify(importSource)};` + code
}
},
},
}
}
22 changes: 10 additions & 12 deletions packages/plugin-react-oxc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { readFileSync } from 'node:fs'
import type { BuildOptions, Plugin } from 'vite'
import {
addRefreshWrapper,
getPreambleCode,
runtimePublicPath,
silenceUseClientWarning,
virtualPreamblePlugin,
} from '@vitejs/react-common'
import { exactRegex } from '@rolldown/pluginutils'

Expand Down Expand Up @@ -143,17 +143,15 @@ export default function viteReact(opts: Options = {}): Plugin[] {
return newCode ? { code: newCode, map: null } : undefined
},
},
transformIndexHtml(_, config) {
if (!skipFastRefresh)
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(config.server!.config.base),
},
]
},
}

return [viteConfig, viteConfigPost, viteRefreshRuntime, viteRefreshWrapper]
return [
viteConfig,
viteConfigPost,
viteRefreshRuntime,
viteRefreshWrapper,
virtualPreamblePlugin({
isEnabled: () => !skipFastRefresh,
}) as any, // rolldown-vite type mismatch
]
}
17 changes: 5 additions & 12 deletions packages/plugin-react-swc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
import type { Plugin } from 'vite'
import {
addRefreshWrapper,
getPreambleCode,
runtimePublicPath,
silenceUseClientWarning,
virtualPreamblePlugin,
} from '@vitejs/react-common'
import * as vite from 'vite'
import { exactRegex } from '@rolldown/pluginutils'
Expand Down Expand Up @@ -165,17 +165,6 @@ const react = (_options?: Options): Plugin[] => {
)
}
},
transformIndexHtml: (_, config) => {
if (!hmrDisabled) {
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(config.server!.config.base),
},
]
}
},
async transform(code, _id, transformOptions) {
const id = _id.split('?')[0]
const refresh = !transformOptions?.ssr && !hmrDisabled
Expand Down Expand Up @@ -205,6 +194,10 @@ const react = (_options?: Options): Plugin[] => {
return { code: newCode ?? result.code, map: result.map }
},
},
virtualPreamblePlugin({
isEnabled: () => !hmrDisabled,
reactRefreshHost: options.reactRefreshHost,
}),
options.plugins
? {
name: 'vite:react-swc',
Expand Down
15 changes: 5 additions & 10 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
preambleCode,
runtimePublicPath,
silenceUseClientWarning,
virtualPreamblePlugin,
} from '@vitejs/react-common'
import {
exactRegex,
Expand Down Expand Up @@ -506,16 +507,6 @@ export default function viteReact(opts: Options = {}): Plugin[] {
}
},
},
transformIndexHtml() {
if (!skipFastRefresh && !isFullBundle)
return [
{
tag: 'script',
attrs: { type: 'module' },
children: getPreambleCode(base),
},
]
},
}

return [
Expand All @@ -524,6 +515,10 @@ export default function viteReact(opts: Options = {}): Plugin[] {
? [viteRefreshWrapper, viteConfigPost, viteReactRefreshFullBundleMode]
: []),
viteReactRefresh,
virtualPreamblePlugin({
isEnabled: () => !skipFastRefresh && !isFullBundle,
reactRefreshHost: opts.reactRefreshHost,
}),
]
}

Expand Down
6 changes: 5 additions & 1 deletion playground/react/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import type { UserConfig } from 'vite'
const config: UserConfig = {
server: { port: 8902 /* Should be unique */ },
mode: 'development',
plugins: [react()],
plugins: [
react({
reactRefreshHost: 'http://localhost:8902',
}),
],
build: {
// to make tests faster
minify: false,
Expand Down
6 changes: 3 additions & 3 deletions playground/ssr-react/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export default defineConfig({
'/src/entry-server.jsx',
)
const appHtml = render(url)
const template = await server.transformIndexHtml(
url,
fs.readFileSync(path.resolve(_dirname, 'index.html'), 'utf-8'),
const template = fs.readFileSync(
path.resolve(_dirname, 'index.html'),
'utf-8',
)
const html = template.replace(`<!--app-html-->`, appHtml)
res.setHeader('content-type', 'text/html').end(html)
Expand Down
6 changes: 5 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading