From b6cdd5621ed9a7b89a010ee17d6fe6a41bd2c7e7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 10 Jul 2020 22:12:25 -0400 Subject: [PATCH] wip: template binding optimization --- packages/compiler-core/src/codegen.ts | 10 ++++++--- packages/compiler-core/src/index.ts | 3 ++- packages/compiler-core/src/options.ts | 10 +++++++++ packages/compiler-core/src/transform.ts | 2 ++ .../src/transforms/transformExpression.ts | 21 +++++++++++++------ packages/compiler-sfc/src/compileScript.ts | 5 +---- packages/compiler-sfc/src/index.ts | 3 ++- packages/compiler-sfc/src/parse.ts | 9 +++----- packages/runtime-core/src/component.ts | 13 +++++++----- packages/runtime-core/src/componentOptions.ts | 7 ++++++- .../runtime-core/src/componentRenderUtils.ts | 16 ++++++++++++-- packages/server-renderer/src/render.ts | 12 ++++++++++- 12 files changed, 81 insertions(+), 30 deletions(-) diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index f01634c72..53551be9b 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -64,7 +64,8 @@ export interface CodegenResult { map?: RawSourceMap } -export interface CodegenContext extends Required { +export interface CodegenContext + extends Omit, '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() diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index ba2587fdd..7cc6219c1 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -6,7 +6,8 @@ export { ParserOptions, TransformOptions, CodegenOptions, - HoistTransform + HoistTransform, + BindingMetadata } from './options' export { baseParse, TextModes } from './parse' export { diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index 952c5cb23..d819b226b 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -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 } diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 92961ef1a..c73e43c54 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -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 diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 6256569f0..c77a8e820 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -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 diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 3bcd2b2fb..408076284 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -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 /** diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index dbaca968e..605562661 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -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' diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index e3291df5a..bcbfabb82 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -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 diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 211ffb857..b467d5794 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -77,10 +77,8 @@ export interface ComponentInternalOptions { __file?: string } -export interface FunctionalComponent< - P = {}, - E extends EmitsOptions = {} -> extends ComponentInternalOptions { +export interface FunctionalComponent

+ extends ComponentInternalOptions { // use of any here is intentional so it can be a valid JSX Element constructor (props: P, ctx: SetupContext): any props?: ComponentPropsOptions

@@ -142,7 +140,12 @@ export interface SetupContext { 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 } diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 083a2c0d0..65138e621 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -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 /** diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 41d44c53f..7f83875a7 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -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 { diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 059c388f7..f8f9ac0b0 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -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)