Skip to content

Commit 68c45e7

Browse files
committed
feat(compiler-sfc): expose properties for more accurate HMR
ref #4358 reverts #4908
1 parent 90083f5 commit 68c45e7

File tree

3 files changed

+61
-11
lines changed

3 files changed

+61
-11
lines changed

packages/compiler-sfc/src/compileScript.ts

+41-7
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export interface SFCScriptCompileOptions {
113113
templateOptions?: Partial<SFCTemplateCompileOptions>
114114
}
115115

116-
interface ImportBinding {
116+
export interface ImportBinding {
117117
isType: boolean
118118
imported: string
119119
source: string
@@ -335,11 +335,7 @@ export function compileScript(
335335

336336
let isUsedInTemplate = true
337337
if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) {
338-
isUsedInTemplate = new RegExp(
339-
// #4274 escape $ since it's a special char in regex
340-
// (and is the only regex special char that is valid in identifiers)
341-
`[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
342-
).test(resolveTemplateUsageCheckString(sfc))
338+
isUsedInTemplate = isImportUsed(local, sfc)
343339
}
344340

345341
userImports[local] = {
@@ -1441,6 +1437,7 @@ export function compileScript(
14411437
return {
14421438
...scriptSetup,
14431439
bindings: bindingMetadata,
1440+
imports: userImports,
14441441
content: s.toString(),
14451442
map: genSourceMap
14461443
? (s.generateMap({
@@ -1960,7 +1957,7 @@ function getObjectOrArrayExpressionKeys(value: Node): string[] {
19601957

19611958
const templateUsageCheckCache = createCache<string>()
19621959

1963-
export function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
1960+
function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
19641961
const { content, ast } = sfc.template!
19651962
const cached = templateUsageCheckCache.get(content)
19661963
if (cached) {
@@ -2018,3 +2015,40 @@ function stripTemplateString(str: string): string {
20182015
}
20192016
return ''
20202017
}
2018+
2019+
function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
2020+
return new RegExp(
2021+
// #4274 escape $ since it's a special char in regex
2022+
// (and is the only regex special char that is valid in identifiers)
2023+
`[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
2024+
).test(resolveTemplateUsageCheckString(sfc))
2025+
}
2026+
2027+
/**
2028+
* Note: this comparison assumes the prev/next script are already identical,
2029+
* and only checks the special case where <script setup lang="ts"> unused import
2030+
* pruning result changes due to template changes.
2031+
*/
2032+
export function hmrShouldReload(
2033+
prevImports: Record<string, ImportBinding>,
2034+
next: SFCDescriptor
2035+
): boolean {
2036+
if (
2037+
!next.scriptSetup ||
2038+
(next.scriptSetup.lang !== 'ts' && next.scriptSetup.lang !== 'tsx')
2039+
) {
2040+
return false
2041+
}
2042+
2043+
// for each previous import, check if its used status remain the same based on
2044+
// the next descriptor's template
2045+
for (const key in prevImports) {
2046+
// if an import was previous unused, but now is used, we need to force
2047+
// reload so that the script now includes that import.
2048+
if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next)) {
2049+
return true
2050+
}
2051+
}
2052+
2053+
return false
2054+
}

packages/compiler-sfc/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
export { parse } from './parse'
33
export { compileTemplate } from './compileTemplate'
44
export { compileStyle, compileStyleAsync } from './compileStyle'
5-
export { compileScript, resolveTemplateUsageCheckString } from './compileScript'
5+
export { compileScript } from './compileScript'
66
export { rewriteDefault } from './rewriteDefault'
77
export {
88
shouldTransform as shouldTransformRef,

packages/compiler-sfc/src/parse.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { RawSourceMap, SourceMapGenerator } from 'source-map'
1111
import { TemplateCompiler } from './compileTemplate'
1212
import { parseCssVars } from './cssVars'
1313
import { createCache } from './cache'
14+
import { hmrShouldReload, ImportBinding } from './compileScript'
1415

1516
export interface SFCParseOptions {
1617
filename?: string
@@ -40,6 +41,7 @@ export interface SFCScriptBlock extends SFCBlock {
4041
type: 'script'
4142
setup?: string | boolean
4243
bindings?: BindingMetadata
44+
imports?: Record<string, ImportBinding>
4345
/**
4446
* import('\@babel/types').Statement
4547
*/
@@ -49,6 +51,7 @@ export interface SFCScriptBlock extends SFCBlock {
4951
*/
5052
scriptSetupAst?: any[]
5153
}
54+
5255
export interface SFCStyleBlock extends SFCBlock {
5356
type: 'style'
5457
scoped?: boolean
@@ -64,9 +67,21 @@ export interface SFCDescriptor {
6467
styles: SFCStyleBlock[]
6568
customBlocks: SFCBlock[]
6669
cssVars: string[]
67-
// whether the SFC uses :slotted() modifier.
68-
// this is used as a compiler optimization hint.
70+
/**
71+
* whether the SFC uses :slotted() modifier.
72+
* this is used as a compiler optimization hint.
73+
*/
6974
slotted: boolean
75+
76+
/**
77+
* compare with an existing descriptor to determine whether HMR should perform
78+
* a reload vs. re-render.
79+
*
80+
* Note: this comparison assumes the prev/next script are already identical,
81+
* and only checks the special case where <script setup lang="ts"> unused import
82+
* pruning result changes due to template changes.
83+
*/
84+
shouldForceReload: (prevImports: Record<string, ImportBinding>) => boolean
7085
}
7186

7287
export interface SFCParseResult {
@@ -103,7 +118,8 @@ export function parse(
103118
styles: [],
104119
customBlocks: [],
105120
cssVars: [],
106-
slotted: false
121+
slotted: false,
122+
shouldForceReload: prevImports => hmrShouldReload(prevImports, descriptor)
107123
}
108124

109125
const errors: (CompilerError | SyntaxError)[] = []

0 commit comments

Comments
 (0)