-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
/
fast-refresh.ts
109 lines (95 loc) · 3 KB
/
fast-refresh.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import type { types as t } from '@babel/core'
import fs from 'fs'
export const runtimePublicPath = '/@react-refresh'
const runtimeFilePath = require.resolve(
'react-refresh/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 === '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'
}