]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: further optimize bindings
authorEvan You <yyx990803@gmail.com>
Thu, 12 Nov 2020 21:11:14 +0000 (16:11 -0500)
committerEvan You <yyx990803@gmail.com>
Thu, 12 Nov 2020 21:11:14 +0000 (16:11 -0500)
packages/compiler-core/src/options.ts
packages/compiler-core/src/transforms/hoistStatic.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/vOn.ts
packages/compiler-sfc/src/compileScript.ts
packages/compiler-sfc/src/genCssVars.ts

index 05c3d2cd08207584172e91a29cf979d3b0749334..2a6b79c1642b56c66514288d4297ad1ea7fe8cb6 100644 (file)
@@ -61,8 +61,16 @@ export type HoistTransform = (
   parent: ParentNode
 ) => void
 
+export const enum BindingTypes {
+  DATA = 'data',
+  PROPS = 'props',
+  SETUP = 'setup',
+  CONST = 'const',
+  OPTIONS = 'options'
+}
+
 export interface BindingMetadata {
-  [key: string]: 'data' | 'props' | 'setup' | 'options' | 'setup-raw'
+  [key: string]: BindingTypes
 }
 
 interface SharedTransformCodegenOptions {
index 240f9bacf4f7b453424c00892526963fd9d46e4d..a4131c6d6f6b2e75e64dfc752531b2ed43b36fa1 100644 (file)
@@ -207,11 +207,11 @@ export function getStaticType(
     case NodeTypes.TEXT_CALL:
       return getStaticType(node.content, resultCache)
     case NodeTypes.SIMPLE_EXPRESSION:
-      return node.isConstant
-        ? node.isRuntimeConstant
-          ? StaticType.HAS_RUNTIME_CONSTANT
-          : StaticType.FULL_STATIC
-        : StaticType.NOT_STATIC
+      return node.isRuntimeConstant
+        ? StaticType.HAS_RUNTIME_CONSTANT
+        : node.isConstant
+          ? StaticType.FULL_STATIC
+          : StaticType.NOT_STATIC
     case NodeTypes.COMPOUND_EXPRESSION:
       let returnType = StaticType.FULL_STATIC
       for (let i = 0; i < node.children.length; i++) {
index 937e3a71da8642fabd0b3d82ff2378843afe7572..f1e0fd2b16225b6e24a5e68cbd0c9a808c76311f 100644 (file)
@@ -54,7 +54,7 @@ import {
 } from '../utils'
 import { buildSlots } from './vSlot'
 import { getStaticType } from './hoistStatic'
-import { BindingMetadata } from '../options'
+import { BindingTypes } from '../options'
 
 // some directive transforms (e.g. v-model) may return a symbol for runtime
 // import, which should be used instead of a resolveDirective call.
@@ -253,7 +253,7 @@ export function resolveComponentType(
   // 3. user component (from setup bindings)
   const bindings = context.bindingMetadata
   if (bindings !== EMPTY_OBJ) {
-    const checkType = (type: BindingMetadata[string]) => {
+    const checkType = (type: BindingTypes) => {
       let resolvedTag = tag
       if (
         bindings[resolvedTag] === type ||
@@ -263,17 +263,17 @@ export function resolveComponentType(
         return resolvedTag
       }
     }
-    const tagFromSetup = checkType('setup')
+    const tagFromSetup = checkType(BindingTypes.SETUP)
     if (tagFromSetup) {
       return context.inline
         ? // setup scope bindings may be refs so they need to be unrefed
           `${context.helperString(UNREF)}(${tagFromSetup})`
         : `$setup[${JSON.stringify(tagFromSetup)}]`
     }
-    const tagFromImport = checkType('setup-raw')
-    if (tagFromImport) {
-      // raw setup bindings (e.g. imports) can be used as-is
-      return tagFromImport
+    const tagFromConst = checkType(BindingTypes.CONST)
+    if (tagFromConst) {
+      // constant setup bindings (e.g. imports) can be used as-is
+      return tagFromConst
     }
   }
 
index 87c6c65b806d70a727ec975e086c4f45fb510eed..57039c05a5534289c1588cf32d0ecfb3cbd48139 100644 (file)
@@ -29,6 +29,7 @@ import { validateBrowserExpression } from '../validateExpression'
 import { parse } from '@babel/parser'
 import { walk } from 'estree-walker'
 import { UNREF } from '../runtimeHelpers'
+import { BindingTypes } from '../options'
 
 const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
 
@@ -99,18 +100,26 @@ export function processExpression(
   }
 
   const { inline, bindingMetadata } = context
+
+  // const bindings exposed from setup - we know they never change
+  if (inline && bindingMetadata[node.content] === BindingTypes.CONST) {
+    node.isRuntimeConstant = true
+    return node
+  }
+
   const prefix = (raw: string) => {
     const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
+    if (type === BindingTypes.CONST) {
+      return raw
+    }
     if (inline) {
       // setup inline mode
-      if (type === 'setup') {
+      if (type === BindingTypes.SETUP) {
         return `${context.helperString(UNREF)}(${raw})`
-      } else if (type === 'props') {
+      } else if (type === BindingTypes.PROPS) {
         // use __props which is generated by compileScript so in ts mode
         // it gets correct type
         return `__props.${raw}`
-      } else if (type === 'setup-raw') {
-        return raw
       }
     }
     // fallback to normal
index 441e6fd1674a15e9c6e5ca983ba8961726a09214..546f669e2aece1ce9c5ece249c3af53f59e687ba 100644 (file)
@@ -70,7 +70,7 @@ export const transformOn: DirectiveTransform = (
   if (exp && !exp.content.trim()) {
     exp = undefined
   }
-  let isCacheable: boolean = context.cacheHandlers && !exp
+  let shouldCache: boolean = context.cacheHandlers && !exp
   if (exp) {
     const isMemberExp = isMemberExpression(exp.content)
     const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
@@ -83,8 +83,11 @@ export const transformOn: DirectiveTransform = (
       isInlineStatement && context.removeIdentifiers(`$event`)
       // with scope analysis, the function is hoistable if it has no reference
       // to scope variables.
-      isCacheable =
+      shouldCache =
         context.cacheHandlers &&
+        // runtime constants don't need to be cached
+        // (this is analyzed by compileScript in SFC <script setup>)
+        !(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.isRuntimeConstant) &&
         // #1541 bail if this is a member exp handler passed to a component -
         // we need to use the original function to preserve arity,
         // e.g. <transition> relies on checking cb.length to determine
@@ -98,7 +101,7 @@ export const transformOn: DirectiveTransform = (
       // to a function, turn it into invocation (and wrap in an arrow function
       // below) so that it always accesses the latest value when called - thus
       // avoiding the need to be patched.
-      if (isCacheable && isMemberExp) {
+      if (shouldCache && isMemberExp) {
         if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
           exp.content += `(...args)`
         } else {
@@ -116,7 +119,7 @@ export const transformOn: DirectiveTransform = (
       )
     }
 
-    if (isInlineStatement || (isCacheable && isMemberExp)) {
+    if (isInlineStatement || (shouldCache && isMemberExp)) {
       // wrap inline statement in a function expression
       exp = createCompoundExpression([
         `${isInlineStatement ? `$event` : `(...args)`} => ${
@@ -142,7 +145,7 @@ export const transformOn: DirectiveTransform = (
     ret = augmentor(ret)
   }
 
-  if (isCacheable) {
+  if (shouldCache) {
     // cache handlers so that it's always the same handler being passed down.
     // this avoids unnecessary re-renders when users use inline handlers on
     // components.
index 4306fa37b65de1fc52f43d0d859bb7076471cc2b..ea5bf95f0eae07e4daed55b4e7ad3cd2e4a552c4 100644 (file)
@@ -26,6 +26,7 @@ import { walk } from 'estree-walker'
 import { RawSourceMap } from 'source-map'
 import { genCssVarsCode, injectCssVarsCalls } from './genCssVars'
 import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
+import { BindingTypes } from 'packages/compiler-core/src/options'
 
 const CTX_FN_NAME = 'defineContext'
 
@@ -134,8 +135,11 @@ export function compileScript(
       source: string
     }
   > = Object.create(null)
-  const setupBindings: Record<string, 'var' | 'const'> = Object.create(null)
-  const refBindings: Record<string, 'var'> = Object.create(null)
+  const setupBindings: Record<
+    string,
+    BindingTypes.SETUP | BindingTypes.CONST
+  > = Object.create(null)
+  const refBindings: Record<string, BindingTypes.SETUP> = Object.create(null)
   const refIdentifiers: Set<Identifier> = new Set()
   const enableRefSugar = options.refSugar !== false
   let defaultExport: Node | undefined
@@ -222,7 +226,7 @@ export function compileScript(
     if (id.name[0] === '$') {
       error(`ref variable identifiers cannot start with $.`, id)
     }
-    refBindings[id.name] = setupBindings[id.name] = 'var'
+    refBindings[id.name] = setupBindings[id.name] = BindingTypes.SETUP
     refIdentifiers.add(id)
   }
 
@@ -712,9 +716,9 @@ export function compileScript(
 }`
   }
 
-  const allBindings = { ...setupBindings }
+  const allBindings: Record<string, any> = { ...setupBindings }
   for (const key in userImports) {
-    allBindings[key] = 'var'
+    allBindings[key] = true
   }
 
   // 9. inject `useCssVars` calls
@@ -737,7 +741,7 @@ export function compileScript(
   }
   if (setupContextType) {
     for (const key in typeDeclaredProps) {
-      bindingMetadata[key] = 'props'
+      bindingMetadata[key] = BindingTypes.PROPS
     }
   }
   if (setupContextArg) {
@@ -745,15 +749,16 @@ export function compileScript(
   }
   if (options.inlineTemplate) {
     for (const [key, { source }] of Object.entries(userImports)) {
-      bindingMetadata[key] = source.endsWith('.vue') ? 'setup-raw' : 'setup'
+      bindingMetadata[key] = source.endsWith('.vue')
+        ? BindingTypes.CONST
+        : BindingTypes.SETUP
     }
     for (const key in setupBindings) {
-      bindingMetadata[key] =
-        setupBindings[key] === 'var' ? 'setup' : 'setup-raw'
+      bindingMetadata[key] = setupBindings[key]
     }
   } else {
     for (const key in allBindings) {
-      bindingMetadata[key] = 'setup'
+      bindingMetadata[key] = BindingTypes.SETUP
     }
   }
 
@@ -867,25 +872,36 @@ export function compileScript(
   }
 }
 
-function walkDeclaration(node: Declaration, bindings: Record<string, string>) {
+function walkDeclaration(
+  node: Declaration,
+  bindings: Record<string, BindingTypes>
+) {
   if (node.type === 'VariableDeclaration') {
     const isConst = node.kind === 'const'
     // export const foo = ...
     for (const { id, init } of node.declarations) {
+      const isContextCall = !!(
+        isConst &&
+        init &&
+        init.type === 'CallExpression' &&
+        init.callee.type === 'Identifier' &&
+        init.callee.name === CTX_FN_NAME
+      )
       if (id.type === 'Identifier') {
         bindings[id.name] =
           // if a declaration is a const literal, we can mark it so that
           // the generated render fn code doesn't need to unref() it
-          isConst &&
+          isContextCall ||
+          (isConst &&
           init!.type !== 'Identifier' && // const a = b
           init!.type !== 'CallExpression' && // const a = ref()
-          init!.type !== 'MemberExpression' // const a = b.c
-            ? 'const'
-            : 'var'
+            init!.type !== 'MemberExpression') // const a = b.c
+            ? BindingTypes.CONST
+            : BindingTypes.SETUP
       } else if (id.type === 'ObjectPattern') {
-        walkObjectPattern(id, bindings, isConst)
+        walkObjectPattern(id, bindings, isConst, isContextCall)
       } else if (id.type === 'ArrayPattern') {
-        walkArrayPattern(id, bindings, isConst)
+        walkArrayPattern(id, bindings, isConst, isContextCall)
       }
     }
   } else if (
@@ -894,14 +910,15 @@ function walkDeclaration(node: Declaration, bindings: Record<string, string>) {
   ) {
     // export function foo() {} / export class Foo {}
     // export declarations must be named.
-    bindings[node.id!.name] = 'const'
+    bindings[node.id!.name] = BindingTypes.CONST
   }
 }
 
 function walkObjectPattern(
   node: ObjectPattern,
-  bindings: Record<string, string>,
-  isConst: boolean
+  bindings: Record<string, BindingTypes>,
+  isConst: boolean,
+  isContextCall = false
 ) {
   for (const p of node.properties) {
     if (p.type === 'ObjectProperty') {
@@ -909,46 +926,58 @@ function walkObjectPattern(
       if (p.key.type === 'Identifier') {
         if (p.key === p.value) {
           // const { x } = ...
-          bindings[p.key.name] = 'var'
+          bindings[p.key.name] = isContextCall
+            ? BindingTypes.CONST
+            : BindingTypes.SETUP
         } else {
-          walkPattern(p.value, bindings, isConst)
+          walkPattern(p.value, bindings, isConst, isContextCall)
         }
       }
     } else {
       // ...rest
       // argument can only be identifer when destructuring
-      bindings[(p.argument as Identifier).name] = isConst ? 'const' : 'var'
+      bindings[(p.argument as Identifier).name] = isConst
+        ? BindingTypes.CONST
+        : BindingTypes.SETUP
     }
   }
 }
 
 function walkArrayPattern(
   node: ArrayPattern,
-  bindings: Record<string, string>,
-  isConst: boolean
+  bindings: Record<string, BindingTypes>,
+  isConst: boolean,
+  isContextCall = false
 ) {
   for (const e of node.elements) {
-    e && walkPattern(e, bindings, isConst)
+    e && walkPattern(e, bindings, isConst, isContextCall)
   }
 }
 
 function walkPattern(
   node: Node,
-  bindings: Record<string, string>,
-  isConst: boolean
+  bindings: Record<string, BindingTypes>,
+  isConst: boolean,
+  isContextCall = false
 ) {
   if (node.type === 'Identifier') {
-    bindings[node.name] = 'var'
+    bindings[node.name] = isContextCall
+      ? BindingTypes.CONST
+      : BindingTypes.SETUP
   } else if (node.type === 'RestElement') {
     // argument can only be identifer when destructuring
-    bindings[(node.argument as Identifier).name] = isConst ? 'const' : 'var'
+    bindings[(node.argument as Identifier).name] = isConst
+      ? BindingTypes.CONST
+      : BindingTypes.SETUP
   } else if (node.type === 'ObjectPattern') {
     walkObjectPattern(node, bindings, isConst)
   } else if (node.type === 'ArrayPattern') {
     walkArrayPattern(node, bindings, isConst)
   } else if (node.type === 'AssignmentPattern') {
     if (node.left.type === 'Identifier') {
-      bindings[node.left.name] = 'var'
+      bindings[node.left.name] = isContextCall
+        ? BindingTypes.CONST
+        : BindingTypes.SETUP
     } else {
       walkPattern(node.left, bindings, isConst)
     }
@@ -1336,7 +1365,7 @@ function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
         // props: ['foo']
         // props: { foo: ... }
         for (const key of getObjectOrArrayExpressionKeys(property)) {
-          bindings[key] = 'props'
+          bindings[key] = BindingTypes.PROPS
         }
       }
 
@@ -1345,7 +1374,7 @@ function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
         // inject: ['foo']
         // inject: { foo: {} }
         for (const key of getObjectOrArrayExpressionKeys(property)) {
-          bindings[key] = 'options'
+          bindings[key] = BindingTypes.OPTIONS
         }
       }
 
@@ -1357,7 +1386,7 @@ function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
         // methods: { foo() {} }
         // computed: { foo() {} }
         for (const key of getObjectExpressionKeys(property.value)) {
-          bindings[key] = 'options'
+          bindings[key] = BindingTypes.OPTIONS
         }
       }
     }
@@ -1380,7 +1409,10 @@ function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
           bodyItem.argument.type === 'ObjectExpression'
         ) {
           for (const key of getObjectExpressionKeys(bodyItem.argument)) {
-            bindings[key] = property.key.name
+            bindings[key] =
+              property.key.name === 'setup'
+                ? BindingTypes.SETUP
+                : BindingTypes.DATA
           }
         }
       }
index d18afe606bca7581834770ccb264e7274174afb9..3d926cf593e9161339a28c7d320ea63f2dad73eb 100644 (file)
@@ -13,7 +13,7 @@ import { ParserPlugin } from '@babel/parser'
 export function genCssVarsCode(
   varsExp: string,
   scoped: boolean,
-  knownBindings?: Record<string, string | boolean>
+  knownBindings?: Record<string, any>
 ) {
   const exp = createSimpleExpression(varsExp, false)
   const context = createTransformContext(createRoot([]), {