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