feat(compiler-sfc): expose properties for more accurate HMR

ref #4358
reverts #4908
This commit is contained in:
Evan You 2021-11-26 14:22:26 +08:00
parent 90083f5718
commit 68c45e73da
3 changed files with 61 additions and 11 deletions

View File

@ -113,7 +113,7 @@ export interface SFCScriptCompileOptions {
templateOptions?: Partial<SFCTemplateCompileOptions> templateOptions?: Partial<SFCTemplateCompileOptions>
} }
interface ImportBinding { export interface ImportBinding {
isType: boolean isType: boolean
imported: string imported: string
source: string source: string
@ -335,11 +335,7 @@ export function compileScript(
let isUsedInTemplate = true let isUsedInTemplate = true
if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) { if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) {
isUsedInTemplate = new RegExp( isUsedInTemplate = isImportUsed(local, sfc)
// #4274 escape $ since it's a special char in regex
// (and is the only regex special char that is valid in identifiers)
`[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
).test(resolveTemplateUsageCheckString(sfc))
} }
userImports[local] = { userImports[local] = {
@ -1441,6 +1437,7 @@ export function compileScript(
return { return {
...scriptSetup, ...scriptSetup,
bindings: bindingMetadata, bindings: bindingMetadata,
imports: userImports,
content: s.toString(), content: s.toString(),
map: genSourceMap map: genSourceMap
? (s.generateMap({ ? (s.generateMap({
@ -1960,7 +1957,7 @@ function getObjectOrArrayExpressionKeys(value: Node): string[] {
const templateUsageCheckCache = createCache<string>() const templateUsageCheckCache = createCache<string>()
export function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
const { content, ast } = sfc.template! const { content, ast } = sfc.template!
const cached = templateUsageCheckCache.get(content) const cached = templateUsageCheckCache.get(content)
if (cached) { if (cached) {
@ -2018,3 +2015,40 @@ function stripTemplateString(str: string): string {
} }
return '' return ''
} }
function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
return new RegExp(
// #4274 escape $ since it's a special char in regex
// (and is the only regex special char that is valid in identifiers)
`[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
).test(resolveTemplateUsageCheckString(sfc))
}
/**
* Note: this comparison assumes the prev/next script are already identical,
* and only checks the special case where <script setup lang="ts"> unused import
* pruning result changes due to template changes.
*/
export function hmrShouldReload(
prevImports: Record<string, ImportBinding>,
next: SFCDescriptor
): boolean {
if (
!next.scriptSetup ||
(next.scriptSetup.lang !== 'ts' && next.scriptSetup.lang !== 'tsx')
) {
return false
}
// for each previous import, check if its used status remain the same based on
// the next descriptor's template
for (const key in prevImports) {
// if an import was previous unused, but now is used, we need to force
// reload so that the script now includes that import.
if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next)) {
return true
}
}
return false
}

View File

@ -2,7 +2,7 @@
export { parse } from './parse' export { parse } from './parse'
export { compileTemplate } from './compileTemplate' export { compileTemplate } from './compileTemplate'
export { compileStyle, compileStyleAsync } from './compileStyle' export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScript, resolveTemplateUsageCheckString } from './compileScript' export { compileScript } from './compileScript'
export { rewriteDefault } from './rewriteDefault' export { rewriteDefault } from './rewriteDefault'
export { export {
shouldTransform as shouldTransformRef, shouldTransform as shouldTransformRef,

View File

@ -11,6 +11,7 @@ import { RawSourceMap, SourceMapGenerator } from 'source-map'
import { TemplateCompiler } from './compileTemplate' import { TemplateCompiler } from './compileTemplate'
import { parseCssVars } from './cssVars' import { parseCssVars } from './cssVars'
import { createCache } from './cache' import { createCache } from './cache'
import { hmrShouldReload, ImportBinding } from './compileScript'
export interface SFCParseOptions { export interface SFCParseOptions {
filename?: string filename?: string
@ -40,6 +41,7 @@ export interface SFCScriptBlock extends SFCBlock {
type: 'script' type: 'script'
setup?: string | boolean setup?: string | boolean
bindings?: BindingMetadata bindings?: BindingMetadata
imports?: Record<string, ImportBinding>
/** /**
* import('\@babel/types').Statement * import('\@babel/types').Statement
*/ */
@ -49,6 +51,7 @@ export interface SFCScriptBlock extends SFCBlock {
*/ */
scriptSetupAst?: any[] scriptSetupAst?: any[]
} }
export interface SFCStyleBlock extends SFCBlock { export interface SFCStyleBlock extends SFCBlock {
type: 'style' type: 'style'
scoped?: boolean scoped?: boolean
@ -64,9 +67,21 @@ export interface SFCDescriptor {
styles: SFCStyleBlock[] styles: SFCStyleBlock[]
customBlocks: SFCBlock[] customBlocks: SFCBlock[]
cssVars: string[] cssVars: string[]
// whether the SFC uses :slotted() modifier. /**
// this is used as a compiler optimization hint. * whether the SFC uses :slotted() modifier.
* this is used as a compiler optimization hint.
*/
slotted: boolean slotted: boolean
/**
* compare with an existing descriptor to determine whether HMR should perform
* a reload vs. re-render.
*
* Note: this comparison assumes the prev/next script are already identical,
* and only checks the special case where <script setup lang="ts"> unused import
* pruning result changes due to template changes.
*/
shouldForceReload: (prevImports: Record<string, ImportBinding>) => boolean
} }
export interface SFCParseResult { export interface SFCParseResult {
@ -103,7 +118,8 @@ export function parse(
styles: [], styles: [],
customBlocks: [], customBlocks: [],
cssVars: [], cssVars: [],
slotted: false slotted: false,
shouldForceReload: prevImports => hmrShouldReload(prevImports, descriptor)
} }
const errors: (CompilerError | SyntaxError)[] = [] const errors: (CompilerError | SyntaxError)[] = []