]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler-sfc): collapse genRuntimeProps logic
authorEvan You <yyx990803@gmail.com>
Tue, 11 Apr 2023 06:06:54 +0000 (14:06 +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/script/context.ts
packages/compiler-sfc/src/script/defineModel.ts
packages/compiler-sfc/src/script/defineProps.ts

index 05bece0ed07c8ec5e7fdb505388533c889880b18..b648def88a06654fdb6b42aa57d37e135dfd57f1 100644 (file)
@@ -55,8 +55,7 @@ import { ScriptCompileContext } from './script/context'
 import {
   processDefineProps,
   DEFINE_PROPS,
-  WITH_DEFAULTS,
-  extractRuntimeProps
+  WITH_DEFAULTS
 } from './script/defineProps'
 import {
   resolveObjectKey,
@@ -279,7 +278,7 @@ export function compileScript(
   }
 
   // metadata that needs to be returned
-  const bindingMetadata: BindingMetadata = {}
+  // const ctx.bindingMetadata: BindingMetadata = {}
   const userImports: Record<string, ImportBinding> = Object.create(null)
   const scriptBindings: Record<string, BindingTypes> = Object.create(null)
   const setupBindings: Record<string, BindingTypes> = Object.create(null)
@@ -1231,7 +1230,6 @@ export function compileScript(
   }
 
   // 4. extract runtime props/emits code from setup context type
-  extractRuntimeProps(ctx)
   if (emitsTypeDecl) {
     extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error)
   }
@@ -1265,31 +1263,28 @@ export function compileScript(
 
   // 7. analyze binding metadata
   if (scriptAst) {
-    Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))
+    Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
   }
   if (ctx.propsRuntimeDecl) {
     for (const key of getObjectOrArrayExpressionKeys(ctx.propsRuntimeDecl)) {
-      bindingMetadata[key] = BindingTypes.PROPS
+      ctx.bindingMetadata[key] = BindingTypes.PROPS
     }
   }
-  for (const key in ctx.typeDeclaredProps) {
-    bindingMetadata[key] = BindingTypes.PROPS
-  }
   for (const key in ctx.modelDecls) {
-    bindingMetadata[key] = BindingTypes.PROPS
+    ctx.bindingMetadata[key] = BindingTypes.PROPS
   }
   // props aliases
   if (ctx.propsDestructureDecl) {
     if (ctx.propsDestructureRestId) {
-      bindingMetadata[ctx.propsDestructureRestId] =
+      ctx.bindingMetadata[ctx.propsDestructureRestId] =
         BindingTypes.SETUP_REACTIVE_CONST
     }
     for (const key in ctx.propsDestructuredBindings) {
       const { local } = ctx.propsDestructuredBindings[key]
       if (local !== key) {
-        bindingMetadata[local] = BindingTypes.PROPS_ALIASED
-        ;(bindingMetadata.__propsAliases ||
-          (bindingMetadata.__propsAliases = {}))[local] = key
+        ctx.bindingMetadata[local] = BindingTypes.PROPS_ALIASED
+        ;(ctx.bindingMetadata.__propsAliases ||
+          (ctx.bindingMetadata.__propsAliases = {}))[local] = key
       }
     }
   }
@@ -1297,7 +1292,7 @@ export function compileScript(
     userImports
   )) {
     if (isType) continue
-    bindingMetadata[key] =
+    ctx.bindingMetadata[key] =
       imported === '*' ||
       (imported === 'default' && source.endsWith('.vue')) ||
       source === 'vue'
@@ -1305,15 +1300,15 @@ export function compileScript(
         : BindingTypes.SETUP_MAYBE_REF
   }
   for (const key in scriptBindings) {
-    bindingMetadata[key] = scriptBindings[key]
+    ctx.bindingMetadata[key] = scriptBindings[key]
   }
   for (const key in setupBindings) {
-    bindingMetadata[key] = setupBindings[key]
+    ctx.bindingMetadata[key] = setupBindings[key]
   }
   // known ref bindings
   if (refBindings) {
     for (const key of refBindings) {
-      bindingMetadata[key] = BindingTypes.SETUP_REF
+      ctx.bindingMetadata[key] = BindingTypes.SETUP_REF
     }
   }
 
@@ -1327,7 +1322,7 @@ export function compileScript(
     ctx.helperImports.add('unref')
     s.prependLeft(
       startOffset,
-      `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`
+      `\n${genCssVarsCode(cssVars, ctx.bindingMetadata, scopeId, isProd)}\n`
     )
   }
 
@@ -1401,7 +1396,7 @@ export function compileScript(
         // generate getter for import bindings
         // skip vue imports since we know they will never change
         returned += `get ${key}() { return ${key} }, `
-      } else if (bindingMetadata[key] === BindingTypes.SETUP_LET) {
+      } else if (ctx.bindingMetadata[key] === BindingTypes.SETUP_LET) {
         // local let binding, also add setter
         const setArg = key === 'v' ? `_v` : `v`
         returned +=
@@ -1434,7 +1429,7 @@ export function compileScript(
             options.templateOptions.compilerOptions),
           inline: true,
           isTS,
-          bindingMetadata
+          bindingMetadata: ctx.bindingMetadata
         }
       })
       if (tips.length) {
@@ -1570,7 +1565,7 @@ export function compileScript(
 
   return {
     ...scriptSetup,
-    bindings: bindingMetadata,
+    bindings: ctx.bindingMetadata,
     imports: userImports,
     content: s.toString(),
     map: genSourceMap
index 0dc94a537a30b25bace27edf67230b325cb3a726..007db8620d77cc88ea7df739520aa504aadf0fce 100644 (file)
@@ -3,12 +3,9 @@ import { SFCDescriptor } from '../parse'
 import { generateCodeFrame } from '@vue/shared'
 import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
 import { SFCScriptCompileOptions } from '../compileScript'
-import {
-  PropsDeclType,
-  PropTypeData,
-  PropsDestructureBindings
-} from './defineProps'
+import { PropsDeclType, PropsDestructureBindings } from './defineProps'
 import { ModelDecl } from './defineModel'
+import { BindingMetadata } from '../../../compiler-core/src'
 
 export class ScriptCompileContext {
   isJS: boolean
@@ -23,12 +20,6 @@ export class ScriptCompileContext {
   scriptStartOffset = this.descriptor.script?.loc.start.offset
   scriptEndOffset = this.descriptor.script?.loc.end.offset
 
-  helperImports: Set<string> = new Set()
-  helper(key: string): string {
-    this.helperImports.add(key)
-    return `_${key}`
-  }
-
   declaredTypes: Record<string, string[]> = Object.create(null)
 
   // macros presence check
@@ -49,11 +40,19 @@ export class ScriptCompileContext {
   propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
   propsDestructureRestId: string | undefined
   propsRuntimeDefaults: Node | undefined
-  typeDeclaredProps: Record<string, PropTypeData> = {}
 
   // defineModel
   modelDecls: Record<string, ModelDecl> = {}
 
+  // codegen
+  bindingMetadata: BindingMetadata = {}
+
+  helperImports: Set<string> = new Set()
+  helper(key: string): string {
+    this.helperImports.add(key)
+    return `_${key}`
+  }
+
   constructor(
     public descriptor: SFCDescriptor,
     public options: SFCScriptCompileOptions
index befe0d95cb5162a011d804aad7da780f1712c859..1aa5c55ecf6c7b1b1948cbadea6e9b9de0322a95 100644 (file)
@@ -9,7 +9,7 @@ export interface ModelDecl {
   identifier: string | undefined
 }
 
-export function genModels(ctx: ScriptCompileContext) {
+export function genModelProps(ctx: ScriptCompileContext) {
   if (!ctx.hasDefineModelCall) return
 
   const isProd = !!ctx.options.isProd
index 9def99cf3c1df9a5f20a3275f5e8b9790a0a48c9..61339373e5e7ab689146db82e91d11c623695a6a 100644 (file)
@@ -9,7 +9,7 @@ import {
   ObjectExpression,
   Expression
 } from '@babel/types'
-import { isFunctionType } from '@vue/compiler-dom'
+import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
 import { ScriptCompileContext } from './context'
 import { inferRuntimeType, resolveQualifiedType } from './resolveType'
 import {
@@ -22,7 +22,7 @@ import {
   unwrapTSNode,
   toRuntimeTypeString
 } from './utils'
-import { genModels } from './defineModel'
+import { genModelProps } from './defineModel'
 
 export const DEFINE_PROPS = 'defineProps'
 export const WITH_DEFAULTS = 'withDefaults'
@@ -174,44 +174,6 @@ function processWithDefaults(
   return true
 }
 
-export function extractRuntimeProps(ctx: ScriptCompileContext) {
-  const node = ctx.propsTypeDecl
-  if (!node) return
-  const members = node.type === 'TSTypeLiteral' ? node.members : node.body
-  for (const m of members) {
-    if (
-      (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
-      m.key.type === 'Identifier'
-    ) {
-      let type: string[] | undefined
-      let skipCheck = false
-      if (m.type === 'TSMethodSignature') {
-        type = ['Function']
-      } else if (m.typeAnnotation) {
-        type = inferRuntimeType(
-          m.typeAnnotation.typeAnnotation,
-          ctx.declaredTypes
-        )
-        // skip check for result containing unknown types
-        if (type.includes(UNKNOWN_TYPE)) {
-          if (type.includes('Boolean') || type.includes('Function')) {
-            type = type.filter(t => t !== UNKNOWN_TYPE)
-            skipCheck = true
-          } else {
-            type = ['null']
-          }
-        }
-      }
-      ctx.typeDeclaredProps[m.key.name] = {
-        key: m.key.name,
-        required: !m.optional,
-        type: type || [`null`],
-        skipCheck
-      }
-    }
-  }
-}
-
 export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
   let propsDecls: undefined | string
   if (ctx.propsRuntimeDecl) {
@@ -234,10 +196,10 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
       }
     }
   } else if (ctx.propsTypeDecl) {
-    propsDecls = genPropsFromTS(ctx)
+    propsDecls = genRuntimePropsFromTypes(ctx)
   }
 
-  const modelsDecls = genModels(ctx)
+  const modelsDecls = genModelProps(ctx)
 
   if (propsDecls && modelsDecls) {
     return `${ctx.helper('mergeModels')}(${propsDecls}, ${modelsDecls})`
@@ -246,71 +208,61 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
   }
 }
 
-function genPropsFromTS(ctx: ScriptCompileContext) {
-  const keys = Object.keys(ctx.typeDeclaredProps)
-  if (!keys.length) return
-
+function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
+  const propStrings: string[] = []
   const hasStaticDefaults = hasStaticWithDefaults(ctx)
-  let propsDecls = `{
-    ${keys
-      .map(key => {
-        let defaultString: string | undefined
-        const destructured = genDestructuredDefaultValue(
-          ctx,
-          key,
-          ctx.typeDeclaredProps[key].type
+
+  // this is only called if propsTypeDecl exists
+  const node = ctx.propsTypeDecl!
+  const members = node.type === 'TSTypeLiteral' ? node.members : node.body
+  for (const m of members) {
+    if (
+      (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
+      m.key.type === 'Identifier'
+    ) {
+      const key = m.key.name
+      let type: string[] | undefined
+      let skipCheck = false
+      if (m.type === 'TSMethodSignature') {
+        type = ['Function']
+      } else if (m.typeAnnotation) {
+        type = inferRuntimeType(
+          m.typeAnnotation.typeAnnotation,
+          ctx.declaredTypes
         )
-        if (destructured) {
-          defaultString = `default: ${destructured.valueString}${
-            destructured.needSkipFactory ? `, skipFactory: true` : ``
-          }`
-        } else if (hasStaticDefaults) {
-          const prop = (
-            ctx.propsRuntimeDefaults as ObjectExpression
-          ).properties.find(node => {
-            if (node.type === 'SpreadElement') return false
-            return resolveObjectKey(node.key, node.computed) === key
-          }) as ObjectProperty | ObjectMethod
-          if (prop) {
-            if (prop.type === 'ObjectProperty') {
-              // prop has corresponding static default value
-              defaultString = `default: ${ctx.getString(prop.value)}`
-            } else {
-              defaultString = `${prop.async ? 'async ' : ''}${
-                prop.kind !== 'method' ? `${prop.kind} ` : ''
-              }default() ${ctx.getString(prop.body)}`
-            }
+        // skip check for result containing unknown types
+        if (type.includes(UNKNOWN_TYPE)) {
+          if (type.includes('Boolean') || type.includes('Function')) {
+            type = type.filter(t => t !== UNKNOWN_TYPE)
+            skipCheck = true
+          } else {
+            type = ['null']
           }
         }
+      }
 
-        const { type, required, skipCheck } = ctx.typeDeclaredProps[key]
-        if (!ctx.options.isProd) {
-          return `${key}: { ${concatStrings([
-            `type: ${toRuntimeTypeString(type)}`,
-            `required: ${required}`,
-            skipCheck && 'skipCheck: true',
-            defaultString
-          ])} }`
-        } else if (
-          type.some(
-            el =>
-              el === 'Boolean' ||
-              ((!hasStaticDefaults || defaultString) && el === 'Function')
-          )
-        ) {
-          // #4783 for boolean, should keep the type
-          // #7111 for function, if default value exists or it's not static, should keep it
-          // in production
-          return `${key}: { ${concatStrings([
-            `type: ${toRuntimeTypeString(type)}`,
-            defaultString
-          ])} }`
-        } else {
-          // production: checks are useless
-          return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
-        }
-      })
-      .join(',\n    ')}\n  }`
+      propStrings.push(
+        genRuntimePropFromType(
+          ctx,
+          key,
+          !m.optional,
+          type || [`null`],
+          skipCheck,
+          hasStaticDefaults
+        )
+      )
+
+      // register bindings
+      ctx.bindingMetadata[key] = BindingTypes.PROPS
+    }
+  }
+
+  if (!propStrings.length) {
+    return
+  }
+
+  let propsDecls = `{
+    ${propStrings.join(',\n    ')}\n  }`
 
   if (ctx.propsRuntimeDefaults && !hasStaticDefaults) {
     propsDecls = `${ctx.helper('mergeDefaults')}(${propsDecls}, ${ctx.getString(
@@ -321,13 +273,73 @@ function genPropsFromTS(ctx: ScriptCompileContext) {
   return propsDecls
 }
 
+function genRuntimePropFromType(
+  ctx: ScriptCompileContext,
+  key: string,
+  required: boolean,
+  type: string[],
+  skipCheck: boolean,
+  hasStaticDefaults: boolean
+): string {
+  let defaultString: string | undefined
+  const destructured = genDestructuredDefaultValue(ctx, key, type)
+  if (destructured) {
+    defaultString = `default: ${destructured.valueString}${
+      destructured.needSkipFactory ? `, skipFactory: true` : ``
+    }`
+  } else if (hasStaticDefaults) {
+    const prop = (ctx.propsRuntimeDefaults as ObjectExpression).properties.find(
+      node => {
+        if (node.type === 'SpreadElement') return false
+        return resolveObjectKey(node.key, node.computed) === key
+      }
+    ) as ObjectProperty | ObjectMethod
+    if (prop) {
+      if (prop.type === 'ObjectProperty') {
+        // prop has corresponding static default value
+        defaultString = `default: ${ctx.getString(prop.value)}`
+      } else {
+        defaultString = `${prop.async ? 'async ' : ''}${
+          prop.kind !== 'method' ? `${prop.kind} ` : ''
+        }default() ${ctx.getString(prop.body)}`
+      }
+    }
+  }
+
+  if (!ctx.options.isProd) {
+    return `${key}: { ${concatStrings([
+      `type: ${toRuntimeTypeString(type)}`,
+      `required: ${required}`,
+      skipCheck && 'skipCheck: true',
+      defaultString
+    ])} }`
+  } else if (
+    type.some(
+      el =>
+        el === 'Boolean' ||
+        ((!hasStaticDefaults || defaultString) && el === 'Function')
+    )
+  ) {
+    // #4783 for boolean, should keep the type
+    // #7111 for function, if default value exists or it's not static, should keep it
+    // in production
+    return `${key}: { ${concatStrings([
+      `type: ${toRuntimeTypeString(type)}`,
+      defaultString
+    ])} }`
+  } else {
+    // production: checks are useless
+    return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
+  }
+}
+
 /**
  * check defaults. If the default object is an object literal with only
  * static properties, we can directly generate more optimized default
  * declarations. Otherwise we will have to fallback to runtime merging.
  */
 function hasStaticWithDefaults(ctx: ScriptCompileContext) {
-  return (
+  return !!(
     ctx.propsRuntimeDefaults &&
     ctx.propsRuntimeDefaults.type === 'ObjectExpression' &&
     ctx.propsRuntimeDefaults.properties.every(