]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: template binding optimization
authorEvan You <yyx990803@gmail.com>
Sat, 11 Jul 2020 02:12:25 +0000 (22:12 -0400)
committerEvan You <yyx990803@gmail.com>
Sat, 11 Jul 2020 02:12:25 +0000 (22:12 -0400)
12 files changed:
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-sfc/src/compileScript.ts
packages/compiler-sfc/src/index.ts
packages/compiler-sfc/src/parse.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/componentRenderUtils.ts
packages/server-renderer/src/render.ts

index f01634c7237ab277f7adf4b7b4741ce3d5c56f0d..53551be9bc7242eedb5442423fd8e58b0fa9a76b 100644 (file)
@@ -64,7 +64,8 @@ export interface CodegenResult {
   map?: RawSourceMap
 }
 
-export interface CodegenContext extends Required<CodegenOptions> {
+export interface CodegenContext
+  extends Omit<Required<CodegenOptions>, 'bindingMetadata'> {
   source: string
   code: string
   line: number
@@ -204,16 +205,19 @@ export function generate(
   }
 
   // enter render function
+  const optimizeSources = options.bindingMetadata
+    ? `, $props, $setup, $data, $options`
+    : ``
   if (!ssr) {
     if (genScopeId) {
       push(`const render = ${PURE_ANNOTATION}_withId(`)
     }
-    push(`function render(_ctx, _cache) {`)
+    push(`function render(_ctx, _cache${optimizeSources}) {`)
   } else {
     if (genScopeId) {
       push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
     }
-    push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
+    push(`function ssrRender(_ctx, _push, _parent, _attrs${optimizeSources}) {`)
   }
   indent()
 
index ba2587fdd74588bd48614819f35b65942600c949..7cc6219c1cbdd5c77319844e074cc83de2accfb1 100644 (file)
@@ -6,7 +6,8 @@ export {
   ParserOptions,
   TransformOptions,
   CodegenOptions,
-  HoistTransform
+  HoistTransform,
+  BindingMetadata
 } from './options'
 export { baseParse, TextModes } from './parse'
 export {
index 952c5cb23f0473372ae96544dab7b9b67fd5085b..d819b226b55be1c38d665e5d509ea7f61714a7c4 100644 (file)
@@ -57,6 +57,10 @@ export type HoistTransform = (
   parent: ParentNode
 ) => void
 
+export interface BindingMetadata {
+  [key: string]: 'data' | 'props' | 'setup' | 'options'
+}
+
 export interface TransformOptions {
   /**
    * An array of node transforms to be applied to every AST node.
@@ -122,6 +126,11 @@ export interface TransformOptions {
    * `ssrRender` option instead of `render`.
    */
   ssr?: boolean
+  /**
+   * Optional binding metadata analyzed from script - used to optimize
+   * binding access when `prefixIdentifiers` is enabled.
+   */
+  bindingMetadata?: BindingMetadata
   onError?: (error: CompilerError) => void
 }
 
@@ -169,6 +178,7 @@ export interface CodegenOptions {
   runtimeGlobalName?: string
   // we need to know this during codegen to generate proper preambles
   prefixIdentifiers?: boolean
+  bindingMetadata?: BindingMetadata
   // generate ssr-specific code?
   ssr?: boolean
 }
index 92961ef1aeef6b9de5e6abd18f6d40f9f88f2eae..c73e43c5457259c77c6b6330535c5b3f638b57a7 100644 (file)
@@ -120,6 +120,7 @@ export function createTransformContext(
     expressionPlugins = [],
     scopeId = null,
     ssr = false,
+    bindingMetadata = {},
     onError = defaultOnError
   }: TransformOptions
 ): TransformContext {
@@ -135,6 +136,7 @@ export function createTransformContext(
     expressionPlugins,
     scopeId,
     ssr,
+    bindingMetadata,
     onError,
 
     // state
index 6256569f04cb5f528116959162c4047fdfec1a1b..c77a8e8208be36b03df89aa1f8506bd912f458d9 100644 (file)
@@ -1,8 +1,8 @@
 // - Parse expressions in templates into compound expressions so that each
 //   identifier gets more accurate source-map locations.
 //
-// - Prefix identifiers with `_ctx.` so that they are accessed from the render
-//   context
+// - Prefix identifiers with `_ctx.` or `$xxx` (for known binding types) so that
+//   they are accessed from the right source
 //
 // - This transform is only applied in non-browser builds because it relies on
 //   an additional JavaScript parser. In the browser, there is no source-map
@@ -25,7 +25,8 @@ import {
 import {
   isGloballyWhitelisted,
   makeMap,
-  babelParserDefautPlugins
+  babelParserDefautPlugins,
+  hasOwn
 } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
@@ -99,6 +100,14 @@ export function processExpression(
     return node
   }
 
+  const { bindingMetadata } = context
+  const prefix = (raw: string) => {
+    const source = hasOwn(bindingMetadata, raw)
+      ? `$` + bindingMetadata[raw]
+      : `_ctx`
+    return `${source}.${raw}`
+  }
+
   // fast path if expression is a simple identifier.
   const rawExp = node.content
   // bail on parens to prevent any possible function invocations.
@@ -110,7 +119,7 @@ export function processExpression(
       !isGloballyWhitelisted(rawExp) &&
       !isLiteralWhitelisted(rawExp)
     ) {
-      node.content = `_ctx.${rawExp}`
+      node.content = prefix(rawExp)
     } else if (!context.identifiers[rawExp] && !bailConstant) {
       // mark node constant for hoisting unless it's referring a scope variable
       node.isConstant = true
@@ -148,7 +157,7 @@ export function processExpression(
   const isDuplicate = (node: Node & PrefixMeta): boolean =>
     ids.some(id => id.start === node.start)
 
-  // walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
+  // walk the AST and look for identifiers that need to be prefixed.
   walkJS(ast, {
     enter(node: Node & PrefixMeta, parent) {
       if (node.type === 'Identifier') {
@@ -160,7 +169,7 @@ export function processExpression(
               // rewrite the value
               node.prefix = `${node.name}: `
             }
-            node.name = `_ctx.${node.name}`
+            node.name = prefix(node.name)
             ids.push(node)
           } else if (!isStaticPropertyKey(node, parent)) {
             // The identifier is considered constant unless it's pointing to a
index 3bcd2b2fbb207d5757839e2bead35d335e6e3b3c..4080762848aa9b122ea8fe5d3c5ac5cb12fa7297 100644 (file)
@@ -1,4 +1,5 @@
 import MagicString from 'magic-string'
+import { BindingMetadata } from '@vue/compiler-core'
 import { SFCDescriptor, SFCScriptBlock } from './parse'
 import { parse, ParserPlugin } from '@babel/parser'
 import { babelParserDefautPlugins, generateCodeFrame } from '@vue/shared'
@@ -28,10 +29,6 @@ export interface SFCScriptCompileOptions {
   babelParserPlugins?: ParserPlugin[]
 }
 
-export interface BindingMetadata {
-  [key: string]: 'data' | 'props' | 'setup' | 'ctx'
-}
-
 let hasWarned = false
 
 /**
index dbaca968ed9372b14d39021b3f9db5e5073f0adc..605562661c2c85c9a83191eb658e37f04acd7d40 100644 (file)
@@ -24,9 +24,10 @@ export {
   SFCAsyncStyleCompileOptions,
   SFCStyleCompileResults
 } from './compileStyle'
-export { SFCScriptCompileOptions, BindingMetadata } from './compileScript'
+export { SFCScriptCompileOptions } from './compileScript'
 export {
   CompilerOptions,
   CompilerError,
+  BindingMetadata,
   generateCodeFrame
 } from '@vue/compiler-core'
index e3291df5a34e69d5ff1640d03826759edb3e2a52..bcbfabb82d5a059694a0c02b8bbbbdf031778840 100644 (file)
@@ -3,17 +3,14 @@ import {
   ElementNode,
   SourceLocation,
   CompilerError,
-  TextModes
+  TextModes,
+  BindingMetadata
 } from '@vue/compiler-core'
 import * as CompilerDOM from '@vue/compiler-dom'
 import { RawSourceMap, SourceMapGenerator } from 'source-map'
 import { generateCodeFrame } from '@vue/shared'
 import { TemplateCompiler } from './compileTemplate'
-import {
-  compileScript,
-  BindingMetadata,
-  SFCScriptCompileOptions
-} from './compileScript'
+import { compileScript, SFCScriptCompileOptions } from './compileScript'
 
 export interface SFCParseOptions extends SFCScriptCompileOptions {
   filename?: string
index 211ffb8575a28e6a117d8e0de9bcddfa10307f5f..b467d57948148fae2cc9b4af0c3ec393d04c4415 100644 (file)
@@ -77,10 +77,8 @@ export interface ComponentInternalOptions {
   __file?: string
 }
 
-export interface FunctionalComponent<
-  P = {},
-  E extends EmitsOptions = {}
-> extends ComponentInternalOptions {
+export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
+  extends ComponentInternalOptions {
   // use of any here is intentional so it can be a valid JSX Element constructor
   (props: P, ctx: SetupContext<E>): any
   props?: ComponentPropsOptions<P>
@@ -142,7 +140,12 @@ export interface SetupContext<E = ObjectEmitsOptions> {
 export type InternalRenderFunction = {
   (
     ctx: ComponentPublicInstance,
-    cache: ComponentInternalInstance['renderCache']
+    cache: ComponentInternalInstance['renderCache'],
+    // for compiler-optimized bindings
+    $props: ComponentInternalInstance['props'],
+    $setup: ComponentInternalInstance['setupState'],
+    $data: ComponentInternalInstance['data'],
+    $options: ComponentInternalInstance['ctx']
   ): VNodeChild
   _rc?: boolean // isRuntimeCompiled
 }
index 083a2c0d0f7434f1614de38e15fea72fd3ecf6e7..65138e621a617a18c9ea2875bfb6151cc7c40246 100644 (file)
@@ -119,7 +119,12 @@ export interface ComponentOptionsBase<
     ctx: any,
     push: (item: any) => void,
     parentInstance: ComponentInternalInstance,
-    attrs?: Data
+    attrs: Data | undefined,
+    // for compiler-optimized bindings
+    $props: ComponentInternalInstance['props'],
+    $setup: ComponentInternalInstance['setupState'],
+    $data: ComponentInternalInstance['data'],
+    $options: ComponentInternalInstance['ctx']
   ) => void
 
   /**
index 41d44c53fc49e1347a82cfe703d4df2a2951a4a1..7f83875a7ccbf2188d293ab717da8288e691a875 100644 (file)
@@ -50,7 +50,11 @@ export function renderComponentRoot(
     slots,
     attrs,
     emit,
-    renderCache
+    render,
+    renderCache,
+    data,
+    setupState,
+    ctx
   } = instance
 
   let result
@@ -65,7 +69,15 @@ export function renderComponentRoot(
       // runtime-compiled render functions using `with` block.
       const proxyToUse = withProxy || proxy
       result = normalizeVNode(
-        instance.render!.call(proxyToUse, proxyToUse!, renderCache)
+        render!.call(
+          proxyToUse,
+          proxyToUse!,
+          renderCache,
+          props,
+          setupState,
+          data,
+          ctx
+        )
       )
       fallthroughAttrs = attrs
     } else {
index 059c388f793a7b5ea88f86416adfcc89e27362ea..f8f9ac0b06bd7833f1a292b6206e3a5bf5e16f73 100644 (file)
@@ -126,7 +126,17 @@ function renderComponentSubTree(
 
       // set current rendering instance for asset resolution
       setCurrentRenderingInstance(instance)
-      comp.ssrRender(instance.proxy, push, instance, attrs)
+      comp.ssrRender(
+        instance.proxy,
+        push,
+        instance,
+        attrs,
+        // compiler-optimized bindings
+        instance.props,
+        instance.setupState,
+        instance.data,
+        instance.ctx
+      )
       setCurrentRenderingInstance(null)
     } else if (instance.render) {
       renderVNode(push, renderComponentRoot(instance), instance)