]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-sfc): expose properties for more accurate HMR
authorEvan You <yyx990803@gmail.com>
Fri, 26 Nov 2021 06:22:26 +0000 (14:22 +0800)
committerEvan You <yyx990803@gmail.com>
Fri, 26 Nov 2021 06:22:26 +0000 (14:22 +0800)
ref #4358
reverts #4908

packages/compiler-sfc/src/compileScript.ts
packages/compiler-sfc/src/index.ts
packages/compiler-sfc/src/parse.ts

index 4a33f6e9d8db0096ddc023cf6fc9e0104ab53592..379ad6ab33fe0e6ea2c655866cc1562123e9e3bc 100644 (file)
@@ -113,7 +113,7 @@ export interface SFCScriptCompileOptions {
   templateOptions?: Partial<SFCTemplateCompileOptions>
 }
 
-interface ImportBinding {
+export interface ImportBinding {
   isType: boolean
   imported: string
   source: string
@@ -335,11 +335,7 @@ export function compileScript(
 
     let isUsedInTemplate = true
     if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) {
-      isUsedInTemplate = 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))
+      isUsedInTemplate = isImportUsed(local, sfc)
     }
 
     userImports[local] = {
@@ -1441,6 +1437,7 @@ export function compileScript(
   return {
     ...scriptSetup,
     bindings: bindingMetadata,
+    imports: userImports,
     content: s.toString(),
     map: genSourceMap
       ? (s.generateMap({
@@ -1960,7 +1957,7 @@ function getObjectOrArrayExpressionKeys(value: Node): string[] {
 
 const templateUsageCheckCache = createCache<string>()
 
-export function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
+function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
   const { content, ast } = sfc.template!
   const cached = templateUsageCheckCache.get(content)
   if (cached) {
@@ -2018,3 +2015,40 @@ function stripTemplateString(str: string): string {
   }
   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
+}
index fc835b66a371ac7f635a48b9202a74b86aab5b12..1c4bb14ee6f33fdfcf447bd452d9d1486d979be2 100644 (file)
@@ -2,7 +2,7 @@
 export { parse } from './parse'
 export { compileTemplate } from './compileTemplate'
 export { compileStyle, compileStyleAsync } from './compileStyle'
-export { compileScript, resolveTemplateUsageCheckString } from './compileScript'
+export { compileScript } from './compileScript'
 export { rewriteDefault } from './rewriteDefault'
 export {
   shouldTransform as shouldTransformRef,
index 946435f6b9eafd5ba8add6cf0a21557827d7e0f0..ed44e53682934e5d01affa1da03a49d55e7f0b91 100644 (file)
@@ -11,6 +11,7 @@ import { RawSourceMap, SourceMapGenerator } from 'source-map'
 import { TemplateCompiler } from './compileTemplate'
 import { parseCssVars } from './cssVars'
 import { createCache } from './cache'
+import { hmrShouldReload, ImportBinding } from './compileScript'
 
 export interface SFCParseOptions {
   filename?: string
@@ -40,6 +41,7 @@ export interface SFCScriptBlock extends SFCBlock {
   type: 'script'
   setup?: string | boolean
   bindings?: BindingMetadata
+  imports?: Record<string, ImportBinding>
   /**
    * import('\@babel/types').Statement
    */
@@ -49,6 +51,7 @@ export interface SFCScriptBlock extends SFCBlock {
    */
   scriptSetupAst?: any[]
 }
+
 export interface SFCStyleBlock extends SFCBlock {
   type: 'style'
   scoped?: boolean
@@ -64,9 +67,21 @@ export interface SFCDescriptor {
   styles: SFCStyleBlock[]
   customBlocks: SFCBlock[]
   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
+
+  /**
+   * 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 {
@@ -103,7 +118,8 @@ export function parse(
     styles: [],
     customBlocks: [],
     cssVars: [],
-    slotted: false
+    slotted: false,
+    shouldForceReload: prevImports => hmrShouldReload(prevImports, descriptor)
   }
 
   const errors: (CompilerError | SyntaxError)[] = []