Skip to content

Commit c1091da

Browse files
committed
feat(vue-jsx-vapor): support hmr
1 parent 4cbf303 commit c1091da

File tree

9 files changed

+345
-108
lines changed

9 files changed

+345
-108
lines changed

packages/macros/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,15 @@
175175
"@vue-macros/common": "catalog:",
176176
"@vue/compiler-sfc": "catalog:",
177177
"@vue/language-core": "^2.2.8",
178-
"hash-sum": "^2.0.0",
178+
"hash-sum": "catalog:",
179179
"ts-macro": "catalog:",
180180
"unplugin": "catalog:"
181181
},
182182
"devDependencies": {
183183
"@babel/types": "catalog:",
184184
"@nuxt/kit": "catalog:",
185185
"@nuxt/schema": "catalog:",
186-
"@types/hash-sum": "^1.0.2",
186+
"@types/hash-sum": "catalog:",
187187
"@vue-macros/test-utils": "catalog:",
188188
"vue": "catalog:"
189189
}

packages/vue-jsx-vapor/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,12 @@
217217
"dependencies": {
218218
"@babel/core": "catalog:",
219219
"@babel/plugin-transform-typescript": "catalog:",
220+
"@types/hash-sum": "catalog:",
220221
"@vue-jsx-vapor/babel": "workspace:*",
221222
"@vue-jsx-vapor/compiler": "workspace:*",
222223
"@vue-jsx-vapor/macros": "workspace:*",
223224
"@vue-macros/volar": "catalog:",
225+
"hash-sum": "catalog:",
224226
"ts-macro": "catalog:",
225227
"unplugin": "catalog:",
226228
"unplugin-utils": "catalog:"
@@ -230,6 +232,6 @@
230232
"@nuxt/schema": "catalog:",
231233
"@types/babel__core": "catalog:",
232234
"csstype": "^3.1.3",
233-
"vue": "catalog:"
235+
"vue": "link:/Users/zmj/Documents/core/packages/vue"
234236
}
235237
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import getHash from 'hash-sum'
2+
import { normalizePath } from 'unplugin-utils'
3+
import type { BabelFileResult, types } from '@babel/core'
4+
5+
interface HotComponent {
6+
local: string
7+
exported: string
8+
id: string
9+
}
10+
11+
export function registerHMR(
12+
result: BabelFileResult,
13+
id: string,
14+
defineComponentName = ['defineComponent', 'defineVaporComponent'],
15+
) {
16+
const { ast } = result
17+
18+
// check for hmr injection
19+
const declaredComponents: string[] = []
20+
const hotComponents: HotComponent[] = []
21+
let defaultName = ''
22+
const ssr = false
23+
24+
for (const node of ast!.program.body) {
25+
if (node.type === 'VariableDeclaration') {
26+
const names = parseComponentDecls(node, defineComponentName)
27+
if (names.length) {
28+
declaredComponents.push(...names)
29+
}
30+
}
31+
32+
if (node.type === 'ExportNamedDeclaration') {
33+
if (node.declaration && node.declaration.type === 'VariableDeclaration') {
34+
hotComponents.push(
35+
...parseComponentDecls(node.declaration, defineComponentName).map(
36+
(name) => ({
37+
local: name,
38+
exported: name,
39+
id: getHash(id + name),
40+
}),
41+
),
42+
)
43+
} else if (node.specifiers.length) {
44+
for (const spec of node.specifiers) {
45+
if (
46+
spec.type === 'ExportSpecifier' &&
47+
spec.exported.type === 'Identifier'
48+
) {
49+
const matched = declaredComponents.find(
50+
(name) => name === spec.local.name,
51+
)
52+
if (matched) {
53+
hotComponents.push({
54+
local: spec.local.name,
55+
exported: spec.exported.name,
56+
id: getHash(id + spec.exported.name),
57+
})
58+
}
59+
}
60+
}
61+
}
62+
}
63+
64+
if (node.type === 'ExportDefaultDeclaration') {
65+
if (node.declaration.type === 'Identifier') {
66+
const _name = node.declaration.name
67+
const matched = declaredComponents.find((name) => name === _name)
68+
if (matched) {
69+
hotComponents.push({
70+
local: _name,
71+
exported: 'default',
72+
id: getHash(`${id}default`),
73+
})
74+
}
75+
} else if (isDefineComponentCall(node.declaration, defineComponentName)) {
76+
defaultName = (node.declaration.callee as any).name
77+
hotComponents.push({
78+
local: '__default__',
79+
exported: 'default',
80+
id: getHash(`${id}default`),
81+
})
82+
}
83+
}
84+
}
85+
86+
if (hotComponents.length) {
87+
if (defaultName || ssr) {
88+
result.code = `${result.code!.replaceAll(
89+
`export default ${defaultName}`,
90+
`const __default__ = ${defaultName}`,
91+
)}\nexport default __default__`
92+
}
93+
94+
if (!ssr && !/\?vue&type=script/.test(id)) {
95+
let code = result.code
96+
let callbackCode = ``
97+
for (const { local, exported, id } of hotComponents) {
98+
code +=
99+
`\n${local}.__hmrId = "${id}"` +
100+
`\n__VUE_HMR_RUNTIME__.createRecord("${id}", ${local})`
101+
callbackCode += `
102+
if (mod._rerender_only) {
103+
__VUE_HMR_RUNTIME__.rerender(mod['${exported}'].__hmrId, mod['${exported}'].setup)
104+
} else {
105+
__VUE_HMR_RUNTIME__.reload(mod['${exported}'].__hmrId, mod['${exported}'])
106+
}`
107+
}
108+
109+
code += `\nexport const _rerender_only = __VUE_HMR_RUNTIME__.CHANGED_FILE === ${JSON.stringify(normalizePath(id))}`
110+
code += `
111+
if (import.meta.hot) {
112+
import.meta.hot.on('file-changed', ({ file }) => {
113+
__VUE_HMR_RUNTIME__.CHANGED_FILE = file
114+
})
115+
import.meta.hot.accept((mod) => {${callbackCode}\n})
116+
}`
117+
result.code = code
118+
}
119+
120+
// if (ssr) {
121+
// const normalizedId = normalizePath(path.relative(root, id))
122+
// let ssrInjectCode =
123+
// `\nimport { ssrRegisterHelper } from "${ssrRegisterHelperId}"` +
124+
// `\nconst __moduleId = ${JSON.stringify(normalizedId)}`
125+
// for (const { local } of hotComponents) {
126+
// ssrInjectCode += `\nssrRegisterHelper(${local}, __moduleId)`
127+
// }
128+
// result.code += ssrInjectCode
129+
// }
130+
}
131+
}
132+
133+
function parseComponentDecls(
134+
node: types.VariableDeclaration,
135+
fnNames: string[],
136+
) {
137+
const names = []
138+
for (const decl of node.declarations) {
139+
if (
140+
decl.id.type === 'Identifier' &&
141+
isDefineComponentCall(decl.init, fnNames)
142+
) {
143+
names.push(decl.id.name)
144+
}
145+
}
146+
return names
147+
}
148+
149+
function isDefineComponentCall(
150+
node: types.Node | null | undefined,
151+
names: string[],
152+
): node is types.CallExpression {
153+
return !!(
154+
node &&
155+
node.type === 'CallExpression' &&
156+
node.callee.type === 'Identifier' &&
157+
names.includes(node.callee.name)
158+
)
159+
}

packages/vue-jsx-vapor/src/core/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { transformSync } from '@babel/core'
33
import babelTypescript from '@babel/plugin-transform-typescript'
44
import jsx from '@vue-jsx-vapor/babel'
55
import type { Options } from '../options'
6+
import { registerHMR } from './hmr'
67

78
export type { Options }
89

@@ -23,8 +24,11 @@ export function transformVueJsxVapor(
2324
sourceFileName: id,
2425
babelrc: false,
2526
configFile: false,
27+
ast: true,
2628
})
2729

30+
if (result) registerHMR(result, id)
31+
2832
if (result?.code)
2933
return {
3034
code: result.code,

packages/vue-jsx-vapor/src/raw.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ const plugin = (options: Options = {}): UnpluginOptions[] => {
3030
},
3131
}
3232
},
33+
handleHotUpdate(ctx) {
34+
ctx.server.ws.send({
35+
type: 'custom',
36+
event: 'file-changed',
37+
data: { file: normalizePath(ctx.file) },
38+
})
39+
},
3340
},
3441
resolveId(id) {
3542
if (normalizePath(id) === 'vue-jsx-vapor/runtime') return id

playground/src/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ref, type Ref } from 'vue'
1+
import { defineVaporComponent, ref, type Ref } from 'vue'
22
import { useRef } from 'vue-jsx-vapor'
33
import Count2 from './count'
44
import For from './for'
@@ -9,7 +9,7 @@ import Once from './once'
99
import Show from './show'
1010
import Slot from './slot'
1111

12-
export default () => {
12+
export default defineVaporComponent(() => {
1313
const count = ref('1')
1414

1515
const Count = (props: { value: string }) => {
@@ -73,4 +73,4 @@ export default () => {
7373
</fieldset>
7474
</>
7575
)
76-
}
76+
})

playground/src/if.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { ref } from 'vue'
1+
import { defineVaporComponent, ref } from 'vue'
22

3-
export default () => {
3+
export default defineVaporComponent(() => {
44
const count = ref(1)
55

66
const Foo = () => <div style="color: red">2</div>
@@ -38,4 +38,4 @@ export default () => {
3838
</div>
3939
</div>
4040
)
41-
}
41+
})

0 commit comments

Comments
 (0)