]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler-sfc): split more logic
authorEvan You <yyx990803@gmail.com>
Tue, 11 Apr 2023 07:20:55 +0000 (15:20 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 11 Apr 2023 08:05:00 +0000 (16:05 +0800)
packages/compiler-sfc/src/compileScript.ts
packages/compiler-sfc/src/parse.ts
packages/compiler-sfc/src/script/analyzeScriptBindings.ts [new file with mode: 0644]
packages/compiler-sfc/src/script/defineModel.ts
packages/compiler-sfc/src/script/defineProps.ts
packages/compiler-sfc/src/script/definePropsDestructure.ts
packages/compiler-sfc/src/script/importUsageCheck.ts [new file with mode: 0644]
packages/compiler-sfc/src/script/utils.ts

index 538366ac173fa43f669673eaf3f8e9365f31bbb7..3fb435223bc455cfdb6d9d774e4de669e75f7461 100644 (file)
@@ -1,30 +1,21 @@
 import MagicString from 'magic-string'
 import {
-  BindingMetadata,
   BindingTypes,
-  createRoot,
-  NodeTypes,
-  transform,
-  parserOptions,
   UNREF,
-  SimpleExpressionNode,
   isFunctionType,
   walkIdentifiers,
   getImportedName
 } from '@vue/compiler-dom'
 import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
-import { parse as _parse, parseExpression, ParserPlugin } from '@babel/parser'
-import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared'
+import { parse as _parse, ParserPlugin } from '@babel/parser'
+import { generateCodeFrame } from '@vue/shared'
 import {
   Node,
   Declaration,
   ObjectPattern,
-  ObjectExpression,
   ArrayPattern,
   Identifier,
   ExportSpecifier,
-  TSType,
-  ArrayExpression,
   Statement,
   CallExpression,
   AwaitExpression,
@@ -41,7 +32,6 @@ import {
 import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
 import { warnOnce } from './warn'
 import { rewriteDefaultAST } from './rewriteDefault'
-import { createCache } from './cache'
 import { shouldTransform, transformAST } from '@vue/reactivity-transform'
 import { transformDestructuredProps } from './script/definePropsDestructure'
 import { ScriptCompileContext } from './script/context'
@@ -57,23 +47,16 @@ import {
   DEFINE_EMITS
 } from './script/defineEmits'
 import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
-import {
-  resolveObjectKey,
-  UNKNOWN_TYPE,
-  isLiteralNode,
-  unwrapTSNode,
-  isCallOf
-} from './script/utils'
+import { isLiteralNode, unwrapTSNode, isCallOf } from './script/utils'
+import { inferRuntimeType } from './script/resolveType'
+import { analyzeScriptBindings } from './script/analyzeScriptBindings'
+import { isImportUsed } from './script/importUsageCheck'
 
 // Special compiler macros
 const DEFINE_EXPOSE = 'defineExpose'
 const DEFINE_OPTIONS = 'defineOptions'
 const DEFINE_SLOTS = 'defineSlots'
 
-const isBuiltInDir = makeMap(
-  `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
-)
-
 export interface SFCScriptCompileOptions {
   /**
    * Scope ID for prefixing injected CSS variables.
@@ -968,32 +951,10 @@ export function compileScript(
   }
 
   // 7. analyze binding metadata
+  // `defineProps` & `defineModel` also register props bindings
   if (scriptAst) {
     Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
   }
-  if (ctx.propsRuntimeDecl) {
-    for (const key of getObjectOrArrayExpressionKeys(ctx.propsRuntimeDecl)) {
-      ctx.bindingMetadata[key] = BindingTypes.PROPS
-    }
-  }
-  for (const key in ctx.modelDecls) {
-    ctx.bindingMetadata[key] = BindingTypes.PROPS
-  }
-  // props aliases
-  if (ctx.propsDestructureDecl) {
-    if (ctx.propsDestructureRestId) {
-      ctx.bindingMetadata[ctx.propsDestructureRestId] =
-        BindingTypes.SETUP_REACTIVE_CONST
-    }
-    for (const key in ctx.propsDestructuredBindings) {
-      const { local } = ctx.propsDestructuredBindings[key]
-      if (local !== key) {
-        ctx.bindingMetadata[local] = BindingTypes.PROPS_ALIASED
-        ;(ctx.bindingMetadata.__propsAliases ||
-          (ctx.bindingMetadata.__propsAliases = {}))[local] = key
-      }
-    }
-  }
   for (const [key, { isType, imported, source }] of Object.entries(
     userImports
   )) {
@@ -1478,156 +1439,6 @@ function recordType(node: Node, declaredTypes: Record<string, string[]>) {
   }
 }
 
-function inferRuntimeType(
-  node: TSType,
-  declaredTypes: Record<string, string[]>
-): string[] {
-  switch (node.type) {
-    case 'TSStringKeyword':
-      return ['String']
-    case 'TSNumberKeyword':
-      return ['Number']
-    case 'TSBooleanKeyword':
-      return ['Boolean']
-    case 'TSObjectKeyword':
-      return ['Object']
-    case 'TSNullKeyword':
-      return ['null']
-    case 'TSTypeLiteral': {
-      // TODO (nice to have) generate runtime property validation
-      const types = new Set<string>()
-      for (const m of node.members) {
-        if (
-          m.type === 'TSCallSignatureDeclaration' ||
-          m.type === 'TSConstructSignatureDeclaration'
-        ) {
-          types.add('Function')
-        } else {
-          types.add('Object')
-        }
-      }
-      return types.size ? Array.from(types) : ['Object']
-    }
-    case 'TSFunctionType':
-      return ['Function']
-    case 'TSArrayType':
-    case 'TSTupleType':
-      // TODO (nice to have) generate runtime element type/length checks
-      return ['Array']
-
-    case 'TSLiteralType':
-      switch (node.literal.type) {
-        case 'StringLiteral':
-          return ['String']
-        case 'BooleanLiteral':
-          return ['Boolean']
-        case 'NumericLiteral':
-        case 'BigIntLiteral':
-          return ['Number']
-        default:
-          return [UNKNOWN_TYPE]
-      }
-
-    case 'TSTypeReference':
-      if (node.typeName.type === 'Identifier') {
-        if (declaredTypes[node.typeName.name]) {
-          return declaredTypes[node.typeName.name]
-        }
-        switch (node.typeName.name) {
-          case 'Array':
-          case 'Function':
-          case 'Object':
-          case 'Set':
-          case 'Map':
-          case 'WeakSet':
-          case 'WeakMap':
-          case 'Date':
-          case 'Promise':
-            return [node.typeName.name]
-
-          // TS built-in utility types
-          // https://www.typescriptlang.org/docs/handbook/utility-types.html
-          case 'Partial':
-          case 'Required':
-          case 'Readonly':
-          case 'Record':
-          case 'Pick':
-          case 'Omit':
-          case 'InstanceType':
-            return ['Object']
-
-          case 'Uppercase':
-          case 'Lowercase':
-          case 'Capitalize':
-          case 'Uncapitalize':
-            return ['String']
-
-          case 'Parameters':
-          case 'ConstructorParameters':
-            return ['Array']
-
-          case 'NonNullable':
-            if (node.typeParameters && node.typeParameters.params[0]) {
-              return inferRuntimeType(
-                node.typeParameters.params[0],
-                declaredTypes
-              ).filter(t => t !== 'null')
-            }
-            break
-          case 'Extract':
-            if (node.typeParameters && node.typeParameters.params[1]) {
-              return inferRuntimeType(
-                node.typeParameters.params[1],
-                declaredTypes
-              )
-            }
-            break
-          case 'Exclude':
-          case 'OmitThisParameter':
-            if (node.typeParameters && node.typeParameters.params[0]) {
-              return inferRuntimeType(
-                node.typeParameters.params[0],
-                declaredTypes
-              )
-            }
-            break
-        }
-      }
-      // cannot infer, fallback to UNKNOWN: ThisParameterType
-      return [UNKNOWN_TYPE]
-
-    case 'TSParenthesizedType':
-      return inferRuntimeType(node.typeAnnotation, declaredTypes)
-
-    case 'TSUnionType':
-      return flattenTypes(node.types, declaredTypes)
-    case 'TSIntersectionType': {
-      return flattenTypes(node.types, declaredTypes).filter(
-        t => t !== UNKNOWN_TYPE
-      )
-    }
-
-    case 'TSSymbolKeyword':
-      return ['Symbol']
-
-    default:
-      return [UNKNOWN_TYPE] // no runtime check
-  }
-}
-
-function flattenTypes(
-  types: TSType[],
-  declaredTypes: Record<string, string[]>
-): string[] {
-  return [
-    ...new Set(
-      ([] as string[]).concat(
-        ...types.map(t => inferRuntimeType(t, declaredTypes))
-      )
-    )
-  ]
-}
-
 function inferEnumType(node: TSEnumDeclaration): string[] {
   const types = new Set<string>()
   for (const m of node.members) {
@@ -1708,251 +1519,3 @@ function isStaticNode(node: Node): boolean {
       return false
   }
 }
-
-/**
- * Analyze bindings in normal `<script>`
- * Note that `compileScriptSetup` already analyzes bindings as part of its
- * compilation process so this should only be used on single `<script>` SFCs.
- */
-function analyzeScriptBindings(ast: Statement[]): BindingMetadata {
-  for (const node of ast) {
-    if (
-      node.type === 'ExportDefaultDeclaration' &&
-      node.declaration.type === 'ObjectExpression'
-    ) {
-      return analyzeBindingsFromOptions(node.declaration)
-    }
-  }
-  return {}
-}
-
-function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
-  const bindings: BindingMetadata = {}
-  // #3270, #3275
-  // mark non-script-setup so we don't resolve components/directives from these
-  Object.defineProperty(bindings, '__isScriptSetup', {
-    enumerable: false,
-    value: false
-  })
-  for (const property of node.properties) {
-    if (
-      property.type === 'ObjectProperty' &&
-      !property.computed &&
-      property.key.type === 'Identifier'
-    ) {
-      // props
-      if (property.key.name === 'props') {
-        // props: ['foo']
-        // props: { foo: ... }
-        for (const key of getObjectOrArrayExpressionKeys(property.value)) {
-          bindings[key] = BindingTypes.PROPS
-        }
-      }
-
-      // inject
-      else if (property.key.name === 'inject') {
-        // inject: ['foo']
-        // inject: { foo: {} }
-        for (const key of getObjectOrArrayExpressionKeys(property.value)) {
-          bindings[key] = BindingTypes.OPTIONS
-        }
-      }
-
-      // computed & methods
-      else if (
-        property.value.type === 'ObjectExpression' &&
-        (property.key.name === 'computed' || property.key.name === 'methods')
-      ) {
-        // methods: { foo() {} }
-        // computed: { foo() {} }
-        for (const key of getObjectExpressionKeys(property.value)) {
-          bindings[key] = BindingTypes.OPTIONS
-        }
-      }
-    }
-
-    // setup & data
-    else if (
-      property.type === 'ObjectMethod' &&
-      property.key.type === 'Identifier' &&
-      (property.key.name === 'setup' || property.key.name === 'data')
-    ) {
-      for (const bodyItem of property.body.body) {
-        // setup() {
-        //   return {
-        //     foo: null
-        //   }
-        // }
-        if (
-          bodyItem.type === 'ReturnStatement' &&
-          bodyItem.argument &&
-          bodyItem.argument.type === 'ObjectExpression'
-        ) {
-          for (const key of getObjectExpressionKeys(bodyItem.argument)) {
-            bindings[key] =
-              property.key.name === 'setup'
-                ? BindingTypes.SETUP_MAYBE_REF
-                : BindingTypes.DATA
-          }
-        }
-      }
-    }
-  }
-
-  return bindings
-}
-
-function getObjectExpressionKeys(node: ObjectExpression): string[] {
-  const keys = []
-  for (const prop of node.properties) {
-    if (prop.type === 'SpreadElement') continue
-    const key = resolveObjectKey(prop.key, prop.computed)
-    if (key) keys.push(String(key))
-  }
-  return keys
-}
-
-function getArrayExpressionKeys(node: ArrayExpression): string[] {
-  const keys = []
-  for (const element of node.elements) {
-    if (element && element.type === 'StringLiteral') {
-      keys.push(element.value)
-    }
-  }
-  return keys
-}
-
-function getObjectOrArrayExpressionKeys(value: Node): string[] {
-  if (value.type === 'ArrayExpression') {
-    return getArrayExpressionKeys(value)
-  }
-  if (value.type === 'ObjectExpression') {
-    return getObjectExpressionKeys(value)
-  }
-  return []
-}
-
-const templateUsageCheckCache = createCache<string>()
-
-function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
-  const { content, ast } = sfc.template!
-  const cached = templateUsageCheckCache.get(content)
-  if (cached) {
-    return cached
-  }
-
-  let code = ''
-  transform(createRoot([ast]), {
-    nodeTransforms: [
-      node => {
-        if (node.type === NodeTypes.ELEMENT) {
-          if (
-            !parserOptions.isNativeTag!(node.tag) &&
-            !parserOptions.isBuiltInComponent!(node.tag)
-          ) {
-            code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
-          }
-          for (let i = 0; i < node.props.length; i++) {
-            const prop = node.props[i]
-            if (prop.type === NodeTypes.DIRECTIVE) {
-              if (!isBuiltInDir(prop.name)) {
-                code += `,v${capitalize(camelize(prop.name))}`
-              }
-              if (prop.exp) {
-                code += `,${processExp(
-                  (prop.exp as SimpleExpressionNode).content,
-                  prop.name
-                )}`
-              }
-            }
-          }
-        } else if (node.type === NodeTypes.INTERPOLATION) {
-          code += `,${processExp(
-            (node.content as SimpleExpressionNode).content
-          )}`
-        }
-      }
-    ]
-  })
-
-  code += ';'
-  templateUsageCheckCache.set(content, code)
-  return code
-}
-
-const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
-
-function processExp(exp: string, dir?: string): string {
-  if (/ as\s+\w|<.*>|:/.test(exp)) {
-    if (dir === 'slot') {
-      exp = `(${exp})=>{}`
-    } else if (dir === 'on') {
-      exp = `()=>{return ${exp}}`
-    } else if (dir === 'for') {
-      const inMatch = exp.match(forAliasRE)
-      if (inMatch) {
-        const [, LHS, RHS] = inMatch
-        return processExp(`(${LHS})=>{}`) + processExp(RHS)
-      }
-    }
-    let ret = ''
-    // has potential type cast or generic arguments that uses types
-    const ast = parseExpression(exp, { plugins: ['typescript'] })
-    walkIdentifiers(ast, node => {
-      ret += `,` + node.name
-    })
-    return ret
-  }
-  return stripStrings(exp)
-}
-
-function stripStrings(exp: string) {
-  return exp
-    .replace(/'[^']*'|"[^"]*"/g, '')
-    .replace(/`[^`]+`/g, stripTemplateString)
-}
-
-function stripTemplateString(str: string): string {
-  const interpMatch = str.match(/\${[^}]+}/g)
-  if (interpMatch) {
-    return interpMatch.map(m => m.slice(2, -1)).join(',')
-  }
-  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 590a0e61dd157d7e76ea07ceb5028b7e44dc610d..b46c5ea1332ca05cf5ba2df75b67fabaf9d98b34 100644 (file)
@@ -11,7 +11,8 @@ import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
 import { TemplateCompiler } from './compileTemplate'
 import { parseCssVars } from './style/cssVars'
 import { createCache } from './cache'
-import { hmrShouldReload, ImportBinding } from './compileScript'
+import { ImportBinding } from './compileScript'
+import { isImportUsed } from './script/importUsageCheck'
 
 export const DEFAULT_FILENAME = 'anonymous.vue'
 
@@ -429,3 +430,32 @@ function isEmpty(node: ElementNode) {
   }
   return true
 }
+
+/**
+ * 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
+}
diff --git a/packages/compiler-sfc/src/script/analyzeScriptBindings.ts b/packages/compiler-sfc/src/script/analyzeScriptBindings.ts
new file mode 100644 (file)
index 0000000..5adbb1b
--- /dev/null
@@ -0,0 +1,131 @@
+import {
+  ArrayExpression,
+  Node,
+  ObjectExpression,
+  Statement
+} from '@babel/types'
+import { BindingMetadata, BindingTypes } from '@vue/compiler-dom'
+import { resolveObjectKey } from './utils'
+
+/**
+ * Analyze bindings in normal `<script>`
+ * Note that `compileScriptSetup` already analyzes bindings as part of its
+ * compilation process so this should only be used on single `<script>` SFCs.
+ */
+export function analyzeScriptBindings(ast: Statement[]): BindingMetadata {
+  for (const node of ast) {
+    if (
+      node.type === 'ExportDefaultDeclaration' &&
+      node.declaration.type === 'ObjectExpression'
+    ) {
+      return analyzeBindingsFromOptions(node.declaration)
+    }
+  }
+  return {}
+}
+
+function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
+  const bindings: BindingMetadata = {}
+  // #3270, #3275
+  // mark non-script-setup so we don't resolve components/directives from these
+  Object.defineProperty(bindings, '__isScriptSetup', {
+    enumerable: false,
+    value: false
+  })
+  for (const property of node.properties) {
+    if (
+      property.type === 'ObjectProperty' &&
+      !property.computed &&
+      property.key.type === 'Identifier'
+    ) {
+      // props
+      if (property.key.name === 'props') {
+        // props: ['foo']
+        // props: { foo: ... }
+        for (const key of getObjectOrArrayExpressionKeys(property.value)) {
+          bindings[key] = BindingTypes.PROPS
+        }
+      }
+
+      // inject
+      else if (property.key.name === 'inject') {
+        // inject: ['foo']
+        // inject: { foo: {} }
+        for (const key of getObjectOrArrayExpressionKeys(property.value)) {
+          bindings[key] = BindingTypes.OPTIONS
+        }
+      }
+
+      // computed & methods
+      else if (
+        property.value.type === 'ObjectExpression' &&
+        (property.key.name === 'computed' || property.key.name === 'methods')
+      ) {
+        // methods: { foo() {} }
+        // computed: { foo() {} }
+        for (const key of getObjectExpressionKeys(property.value)) {
+          bindings[key] = BindingTypes.OPTIONS
+        }
+      }
+    }
+
+    // setup & data
+    else if (
+      property.type === 'ObjectMethod' &&
+      property.key.type === 'Identifier' &&
+      (property.key.name === 'setup' || property.key.name === 'data')
+    ) {
+      for (const bodyItem of property.body.body) {
+        // setup() {
+        //   return {
+        //     foo: null
+        //   }
+        // }
+        if (
+          bodyItem.type === 'ReturnStatement' &&
+          bodyItem.argument &&
+          bodyItem.argument.type === 'ObjectExpression'
+        ) {
+          for (const key of getObjectExpressionKeys(bodyItem.argument)) {
+            bindings[key] =
+              property.key.name === 'setup'
+                ? BindingTypes.SETUP_MAYBE_REF
+                : BindingTypes.DATA
+          }
+        }
+      }
+    }
+  }
+
+  return bindings
+}
+
+function getObjectExpressionKeys(node: ObjectExpression): string[] {
+  const keys = []
+  for (const prop of node.properties) {
+    if (prop.type === 'SpreadElement') continue
+    const key = resolveObjectKey(prop.key, prop.computed)
+    if (key) keys.push(String(key))
+  }
+  return keys
+}
+
+function getArrayExpressionKeys(node: ArrayExpression): string[] {
+  const keys = []
+  for (const element of node.elements) {
+    if (element && element.type === 'StringLiteral') {
+      keys.push(element.value)
+    }
+  }
+  return keys
+}
+
+export function getObjectOrArrayExpressionKeys(value: Node): string[] {
+  if (value.type === 'ArrayExpression') {
+    return getArrayExpressionKeys(value)
+  }
+  if (value.type === 'ObjectExpression') {
+    return getObjectExpressionKeys(value)
+  }
+  return []
+}
index 0a32cebca44df582c401689e256e953b5890a56d..e2d2317b1a77dcb9efed272dd6392f39e542c92d 100644 (file)
@@ -8,6 +8,7 @@ import {
   toRuntimeTypeString,
   unwrapTSNode
 } from './utils'
+import { BindingTypes } from '@vue/compiler-dom'
 
 export const DEFINE_MODEL = 'defineModel'
 
@@ -51,6 +52,8 @@ export function processDefineModel(
     options: optionsString,
     identifier: declId && declId.type === 'Identifier' ? declId.name : undefined
   }
+  // register binding type
+  ctx.bindingMetadata[modelName] = BindingTypes.PROPS
 
   let runtimeOptions = ''
   if (options) {
index 61339373e5e7ab689146db82e91d11c623695a6a..903d038dbe0d032337ffe812e4e95455754cb9a7 100644 (file)
@@ -1,7 +1,6 @@
 import {
   Node,
   LVal,
-  Identifier,
   TSTypeLiteral,
   TSInterfaceBody,
   ObjectProperty,
@@ -23,6 +22,8 @@ import {
   toRuntimeTypeString
 } from './utils'
 import { genModelProps } from './defineModel'
+import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings'
+import { processPropsDestructure } from './definePropsDestructure'
 
 export const DEFINE_PROPS = 'defineProps'
 export const WITH_DEFAULTS = 'withDefaults'
@@ -57,9 +58,15 @@ export function processDefineProps(
     ctx.error(`duplicate ${DEFINE_PROPS}() call`, node)
   }
   ctx.hasDefinePropsCall = true
-
   ctx.propsRuntimeDecl = node.arguments[0]
 
+  // register bindings
+  if (ctx.propsRuntimeDecl) {
+    for (const key of getObjectOrArrayExpressionKeys(ctx.propsRuntimeDecl)) {
+      ctx.bindingMetadata[key] = BindingTypes.PROPS
+    }
+  }
+
   // call has type parameters - infer runtime types from it
   if (node.typeParameters) {
     if (ctx.propsRuntimeDecl) {
@@ -88,48 +95,7 @@ export function processDefineProps(
   if (declId) {
     // handle props destructure
     if (declId.type === 'ObjectPattern') {
-      ctx.propsDestructureDecl = declId
-      for (const prop of declId.properties) {
-        if (prop.type === 'ObjectProperty') {
-          const propKey = resolveObjectKey(prop.key, prop.computed)
-
-          if (!propKey) {
-            ctx.error(
-              `${DEFINE_PROPS}() destructure cannot use computed key.`,
-              prop.key
-            )
-          }
-
-          if (prop.value.type === 'AssignmentPattern') {
-            // default value { foo = 123 }
-            const { left, right } = prop.value
-            if (left.type !== 'Identifier') {
-              ctx.error(
-                `${DEFINE_PROPS}() destructure does not support nested patterns.`,
-                left
-              )
-            }
-            // store default value
-            ctx.propsDestructuredBindings[propKey] = {
-              local: left.name,
-              default: right
-            }
-          } else if (prop.value.type === 'Identifier') {
-            // simple destructure
-            ctx.propsDestructuredBindings[propKey] = {
-              local: prop.value.name
-            }
-          } else {
-            ctx.error(
-              `${DEFINE_PROPS}() destructure does not support nested patterns.`,
-              prop.value
-            )
-          }
-        } else {
-          // rest spread
-          ctx.propsDestructureRestId = (prop.argument as Identifier).name
-        }
-      }
+      processPropsDestructure(ctx, declId)
     } else {
       ctx.propsIdentifier = ctx.getString(declId)
     }
@@ -176,6 +142,7 @@ function processWithDefaults(
 
 export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
   let propsDecls: undefined | string
+
   if (ctx.propsRuntimeDecl) {
     propsDecls = ctx.getString(ctx.propsRuntimeDecl).trim()
     if (ctx.propsDestructureDecl) {
index 3759f0c662ebb20c0f4db3c7b4c08b32f730f96c..220c7ce41dbcd3643769a5d653e66a86589b5a84 100644 (file)
@@ -3,20 +3,83 @@ import {
   Identifier,
   BlockStatement,
   Program,
-  VariableDeclaration
+  VariableDeclaration,
+  ObjectPattern,
+  Expression
 } from '@babel/types'
 import { walk } from 'estree-walker'
 import {
+  BindingTypes,
   extractIdentifiers,
   isFunctionType,
   isInDestructureAssignment,
   isReferencedIdentifier,
   isStaticProperty,
   walkFunctionParams
-} from '@vue/compiler-core'
+} from '@vue/compiler-dom'
 import { genPropsAccessExp } from '@vue/shared'
-import { isCallOf, unwrapTSNode } from './utils'
+import { isCallOf, resolveObjectKey, unwrapTSNode } from './utils'
 import { ScriptCompileContext } from './context'
+import { DEFINE_PROPS } from './defineProps'
+
+export function processPropsDestructure(
+  ctx: ScriptCompileContext,
+  declId: ObjectPattern
+) {
+  ctx.propsDestructureDecl = declId
+
+  const registerBinding = (
+    key: string,
+    local: string,
+    defaultValue?: Expression
+  ) => {
+    ctx.propsDestructuredBindings[key] = { local, default: defaultValue }
+    if (local !== key) {
+      ctx.bindingMetadata[local] = BindingTypes.PROPS_ALIASED
+      ;(ctx.bindingMetadata.__propsAliases ||
+        (ctx.bindingMetadata.__propsAliases = {}))[local] = key
+    }
+  }
+
+  for (const prop of declId.properties) {
+    if (prop.type === 'ObjectProperty') {
+      const propKey = resolveObjectKey(prop.key, prop.computed)
+
+      if (!propKey) {
+        ctx.error(
+          `${DEFINE_PROPS}() destructure cannot use computed key.`,
+          prop.key
+        )
+      }
+
+      if (prop.value.type === 'AssignmentPattern') {
+        // default value { foo = 123 }
+        const { left, right } = prop.value
+        if (left.type !== 'Identifier') {
+          ctx.error(
+            `${DEFINE_PROPS}() destructure does not support nested patterns.`,
+            left
+          )
+        }
+        registerBinding(propKey, left.name, right)
+      } else if (prop.value.type === 'Identifier') {
+        // simple destructure
+        registerBinding(propKey, prop.value.name)
+      } else {
+        ctx.error(
+          `${DEFINE_PROPS}() destructure does not support nested patterns.`,
+          prop.value
+        )
+      }
+    } else {
+      // rest spread
+      ctx.propsDestructureRestId = (prop.argument as Identifier).name
+      // register binding
+      ctx.bindingMetadata[ctx.propsDestructureRestId] =
+        BindingTypes.SETUP_REACTIVE_CONST
+    }
+  }
+}
 
 /**
  * true -> prop binding
diff --git a/packages/compiler-sfc/src/script/importUsageCheck.ts b/packages/compiler-sfc/src/script/importUsageCheck.ts
new file mode 100644 (file)
index 0000000..59473a9
--- /dev/null
@@ -0,0 +1,113 @@
+import { parseExpression } from '@babel/parser'
+import { SFCDescriptor } from '../parse'
+import {
+  NodeTypes,
+  SimpleExpressionNode,
+  createRoot,
+  parserOptions,
+  transform,
+  walkIdentifiers
+} from '@vue/compiler-dom'
+import { createCache } from '../cache'
+import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
+
+/**
+ * Check if an import is used in the SFC's template. This is used to determine
+ * the properties that should be included in the object returned from setup()
+ * when not using inline mode.
+ */
+export 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))
+}
+
+const templateUsageCheckCache = createCache<string>()
+
+function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
+  const { content, ast } = sfc.template!
+  const cached = templateUsageCheckCache.get(content)
+  if (cached) {
+    return cached
+  }
+
+  let code = ''
+  transform(createRoot([ast]), {
+    nodeTransforms: [
+      node => {
+        if (node.type === NodeTypes.ELEMENT) {
+          if (
+            !parserOptions.isNativeTag!(node.tag) &&
+            !parserOptions.isBuiltInComponent!(node.tag)
+          ) {
+            code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
+          }
+          for (let i = 0; i < node.props.length; i++) {
+            const prop = node.props[i]
+            if (prop.type === NodeTypes.DIRECTIVE) {
+              if (!isBuiltInDirective(prop.name)) {
+                code += `,v${capitalize(camelize(prop.name))}`
+              }
+              if (prop.exp) {
+                code += `,${processExp(
+                  (prop.exp as SimpleExpressionNode).content,
+                  prop.name
+                )}`
+              }
+            }
+          }
+        } else if (node.type === NodeTypes.INTERPOLATION) {
+          code += `,${processExp(
+            (node.content as SimpleExpressionNode).content
+          )}`
+        }
+      }
+    ]
+  })
+
+  code += ';'
+  templateUsageCheckCache.set(content, code)
+  return code
+}
+
+const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
+
+function processExp(exp: string, dir?: string): string {
+  if (/ as\s+\w|<.*>|:/.test(exp)) {
+    if (dir === 'slot') {
+      exp = `(${exp})=>{}`
+    } else if (dir === 'on') {
+      exp = `()=>{return ${exp}}`
+    } else if (dir === 'for') {
+      const inMatch = exp.match(forAliasRE)
+      if (inMatch) {
+        const [, LHS, RHS] = inMatch
+        return processExp(`(${LHS})=>{}`) + processExp(RHS)
+      }
+    }
+    let ret = ''
+    // has potential type cast or generic arguments that uses types
+    const ast = parseExpression(exp, { plugins: ['typescript'] })
+    walkIdentifiers(ast, node => {
+      ret += `,` + node.name
+    })
+    return ret
+  }
+  return stripStrings(exp)
+}
+
+function stripStrings(exp: string) {
+  return exp
+    .replace(/'[^']*'|"[^"]*"/g, '')
+    .replace(/`[^`]+`/g, stripTemplateString)
+}
+
+function stripTemplateString(str: string): string {
+  const interpMatch = str.match(/\${[^}]+}/g)
+  if (interpMatch) {
+    return interpMatch.map(m => m.slice(2, -1)).join(',')
+  }
+  return ''
+}
index c5926e4c35d0cd9b8e9712680000f168003ff7c5..1f4b139463baafdd35fedef995757ffafdb6f725 100644 (file)
@@ -9,7 +9,7 @@ export function resolveObjectKey(node: Node, computed: boolean) {
   switch (node.type) {
     case 'StringLiteral':
     case 'NumericLiteral':
-      return node.value
+      return String(node.value)
     case 'Identifier':
       if (!computed) return node.name
   }