Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 8 additions & 50 deletions packages/plugin-react/src/fast-refresh.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fs from 'node:fs'
import path from 'node:path'
import { createRequire } from 'node:module'
import type { types as t } from '@babel/core'

export const runtimePublicPath = '/@react-refresh'

Expand Down Expand Up @@ -67,14 +66,6 @@ const timeout = `
}
`

const footer = `
if (import.meta.hot) {
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;

__ACCEPT__
}`

const checkAndAccept = `
function isReactRefreshBoundary(mod) {
if (mod == null || typeof mod !== 'object') {
Expand Down Expand Up @@ -109,47 +100,14 @@ import.meta.hot.accept(mod => {
});
`

export function addRefreshWrapper(
code: string,
id: string,
accept: boolean,
): string {
return (
header.replace('__SOURCE__', JSON.stringify(id)) +
code +
footer.replace('__ACCEPT__', accept ? checkAndAccept : timeout)
)
}

export function isRefreshBoundary(ast: t.File): boolean {
// Every export must be a potential React component.
// We'll also perform a runtime check that's more robust as well (isLikelyComponentType).
return ast.program.body.every((node) => {
if (node.type !== 'ExportNamedDeclaration') {
return true
}
const { declaration, specifiers } = node
if (declaration) {
if (declaration.type === 'ClassDeclaration') return false
if (declaration.type === 'VariableDeclaration') {
return declaration.declarations.every((variable) =>
isComponentLikeIdentifier(variable.id),
)
}
if (declaration.type === 'FunctionDeclaration') {
return !!declaration.id && isComponentLikeIdentifier(declaration.id)
}
}
return specifiers.every((spec) => {
return isComponentLikeIdentifier(spec.exported)
})
})
}
const footer = `
if (import.meta.hot) {
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;

function isComponentLikeIdentifier(node: t.Node): boolean {
return node.type === 'Identifier' && isComponentLikeName(node.name)
}
${checkAndAccept}
}`

function isComponentLikeName(name: string): boolean {
return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z'
export function addRefreshWrapper(code: string, id: string): string {
return header.replace('__SOURCE__', JSON.stringify(id)) + code + footer
}
15 changes: 3 additions & 12 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import path from 'node:path'
import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
import type { ParserOptions, TransformOptions } from '@babel/core'
import * as babel from '@babel/core'
import { createFilter, loadEnv, normalizePath, resolveEnvPrefix } from 'vite'
import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
import MagicString from 'magic-string'
import type { SourceMap } from 'magic-string'
import {
addRefreshWrapper,
isRefreshBoundary,
preambleCode,
runtimeCode,
runtimePublicPath,
Expand Down Expand Up @@ -245,7 +244,6 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
}

let ast: t.File | null | undefined
let prependReactImport = false
if (!isProjectFile || isJSX) {
if (!useAutomaticRuntime && isProjectFile) {
Expand Down Expand Up @@ -314,14 +312,8 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
parserPlugins.push('typescript')
}

const transformAsync = ast
? babel.transformFromAstAsync.bind(babel, ast, code)
: babel.transformAsync.bind(babel, code)

const isReasonReact = extension.endsWith('.bs.js')
const result = await transformAsync({
const result = await babel.transformAsync(code, {
...babelOptions,
ast: !isReasonReact,
root: projectRoot,
filename: id,
sourceFileName: filepath,
Expand All @@ -344,8 +336,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
if (result) {
let code = result.code!
if (useFastRefresh && /\$RefreshReg\$\(/.test(code)) {
const accept = isReasonReact || isRefreshBoundary(result.ast!)
code = addRefreshWrapper(code, id, accept)
code = addRefreshWrapper(code, id)
}
return {
code,
Expand Down