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' const _require = createRequire(import.meta.url) const reactRefreshDir = path.dirname( _require.resolve('react-refresh/package.json') ) const runtimeFilePath = path.join( reactRefreshDir, 'cjs/react-refresh-runtime.development.js' ) export const runtimeCode = ` const exports = {} ${fs.readFileSync(runtimeFilePath, 'utf-8')} function debounce(fn, delay) { let handle return () => { clearTimeout(handle) handle = setTimeout(fn, delay) } } exports.performReactRefresh = debounce(exports.performReactRefresh, 16) export default exports ` export const preambleCode = ` import RefreshRuntime from "__BASE__${runtimePublicPath.slice(1)}" RefreshRuntime.injectIntoGlobalHook(window) window.$RefreshReg$ = () => {} window.$RefreshSig$ = () => (type) => type window.__vite_plugin_react_preamble_installed__ = true ` const header = ` import RefreshRuntime from "${runtimePublicPath}"; let prevRefreshReg; let prevRefreshSig; if (import.meta.hot) { if (!window.__vite_plugin_react_preamble_installed__) { throw new Error( "@vitejs/plugin-react can't detect preamble. Something is wrong. " + "See https://github.com/vitejs/vite-plugin-react/pull/11#discussion_r430879201" ); } prevRefreshReg = window.$RefreshReg$; prevRefreshSig = window.$RefreshSig$; window.$RefreshReg$ = (type, id) => { RefreshRuntime.register(type, __SOURCE__ + " " + id) }; window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; }`.replace(/[\n]+/gm, '') const footer = ` if (import.meta.hot) { window.$RefreshReg$ = prevRefreshReg; window.$RefreshSig$ = prevRefreshSig; __ACCEPT__ if (!window.__vite_plugin_react_timeout) { window.__vite_plugin_react_timeout = setTimeout(() => { window.__vite_plugin_react_timeout = 0; RefreshRuntime.performReactRefresh(); }, 30); } }` export function addRefreshWrapper( code: string, id: string, accept: boolean ): string { return ( header.replace('__SOURCE__', JSON.stringify(id)) + code + footer.replace('__ACCEPT__', accept ? 'import.meta.hot.accept();' : '') ) } export function isRefreshBoundary(ast: t.File): boolean { // Every export must be a React component. 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) }) }) } function isComponentLikeIdentifier(node: t.Node): boolean { return node.type === 'Identifier' && isComponentLikeName(node.name) } function isComponentLikeName(name: string): boolean { return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z' }