Skip to content

Commit 4abe3be

Browse files
authoredMar 10, 2025··
fix(plugin-vue): ensure HMR updates styles when SFC is treated as a type dependency (#541)
1 parent 59946d3 commit 4abe3be

File tree

6 files changed

+60
-4
lines changed

6 files changed

+60
-4
lines changed
 

‎packages/plugin-vue/src/handleHotUpdate.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export async function handleHotUpdate(
3131
{ file, modules, read }: HmrContext,
3232
options: ResolvedOptions,
3333
customElement: boolean,
34+
typeDepModules?: ModuleNode[],
3435
): Promise<ModuleNode[] | void> {
3536
const prevDescriptor = getDescriptor(file, options, false, true)
3637
if (!prevDescriptor) {
@@ -172,7 +173,9 @@ export async function handleHotUpdate(
172173
}
173174
debug(`[vue:update(${updateType.join('&')})] ${file}`)
174175
}
175-
return [...affectedModules].filter(Boolean) as ModuleNode[]
176+
return [...affectedModules, ...(typeDepModules || [])].filter(
177+
Boolean,
178+
) as ModuleNode[]
176179
}
177180

178181
export function isEqualBlock(a: SFCBlock | null, b: SFCBlock | null): boolean {

‎packages/plugin-vue/src/index.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import fs from 'node:fs'
2-
import type { Plugin, ViteDevServer } from 'vite'
2+
import type { ModuleNode, Plugin, ViteDevServer } from 'vite'
33
import { createFilter, normalizePath } from 'vite'
44
import type {
55
SFCBlock,
@@ -224,14 +224,22 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
224224
if (options.value.compiler.invalidateTypeCache) {
225225
options.value.compiler.invalidateTypeCache(ctx.file)
226226
}
227+
228+
let typeDepModules: ModuleNode[] | undefined
229+
const matchesFilter = filter.value(ctx.file)
227230
if (typeDepToSFCMap.has(ctx.file)) {
228-
return handleTypeDepChange(typeDepToSFCMap.get(ctx.file)!, ctx)
231+
typeDepModules = handleTypeDepChange(
232+
typeDepToSFCMap.get(ctx.file)!,
233+
ctx,
234+
)
235+
if (!matchesFilter) return typeDepModules
229236
}
230-
if (filter.value(ctx.file)) {
237+
if (matchesFilter) {
231238
return handleHotUpdate(
232239
ctx,
233240
options.value,
234241
customElementFilter.value(ctx.file),
242+
typeDepModules,
235243
)
236244
}
237245
},

‎playground/vue/ExportTypeProps1.vue

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<div class="export-type-props1">{{ props }}</div>
3+
</template>
4+
5+
<script lang="ts">
6+
export interface FooProps {
7+
msg: string
8+
}
9+
</script>
10+
11+
<script setup lang="ts">
12+
const props = defineProps<FooProps>()
13+
</script>
14+
15+
<style>
16+
.export-type-props1 {
17+
color: red;
18+
}
19+
</style>

‎playground/vue/ExportTypeProps2.vue

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<template>
2+
<div class="export-type-props2">{{ props }}</div>
3+
</template>
4+
5+
<script setup lang="ts">
6+
import type { FooProps } from './ExportTypeProps1.vue'
7+
const props = defineProps<FooProps>()
8+
</script>

‎playground/vue/Main.vue

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
<PreCompiledExternalScoped />
3737
<PreCompiledExternalCssModules />
3838
<ParserOptions />
39+
<ExportTypeProps1 msg="msg" />
40+
<ExportTypeProps2 msg="msg" />
3941
</template>
4042

4143
<script setup lang="ts">
@@ -66,6 +68,8 @@ import PreCompiledExternalScoped from './pre-compiled/external-scoped.vue'
6668
import PreCompiledExternalCssModules from './pre-compiled/external-cssmodules.vue'
6769
import ParserOptions from './ParserOptions.vue'
6870
import HmrCircularReference from './HmrCircularReference.vue'
71+
import ExportTypeProps1 from './ExportTypeProps1.vue'
72+
import ExportTypeProps2 from './ExportTypeProps2.vue'
6973
7074
const TsGeneric = defineAsyncComponent(() => import('./TsGeneric.vue'))
7175

‎playground/vue/__tests__/vue.spec.ts

+14
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,20 @@ describe('macro imported types', () => {
409409
),
410410
)
411411
})
412+
413+
test('should hmr when SFC is treated as a type dependency', async () => {
414+
const cls1 = '.export-type-props1'
415+
expect(await getColor(cls1)).toBe('red')
416+
editFile('ExportTypeProps1.vue', (code) => code.replace('red', 'blue'))
417+
await untilUpdated(() => getColor(cls1), 'blue')
418+
419+
const cls2 = '.export-type-props2'
420+
editFile('ExportTypeProps1.vue', (code) => code.replace('msg: string', ''))
421+
await untilUpdated(
422+
() => page.textContent(cls2),
423+
JSON.stringify({}, null, 2),
424+
)
425+
})
412426
})
413427

414428
test('TS with generics', async () => {

0 commit comments

Comments
 (0)
Please sign in to comment.