map?: RawSourceMap
}
-export interface CodegenContext extends Required<CodegenOptions> {
+export interface CodegenContext
+ extends Omit<Required<CodegenOptions>, 'bindingMetadata'> {
source: string
code: string
line: number
}
// 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()
ParserOptions,
TransformOptions,
CodegenOptions,
- HoistTransform
+ HoistTransform,
+ BindingMetadata
} from './options'
export { baseParse, TextModes } from './parse'
export {
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.
* `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
}
runtimeGlobalName?: string
// we need to know this during codegen to generate proper preambles
prefixIdentifiers?: boolean
+ bindingMetadata?: BindingMetadata
// generate ssr-specific code?
ssr?: boolean
}
expressionPlugins = [],
scopeId = null,
ssr = false,
+ bindingMetadata = {},
onError = defaultOnError
}: TransformOptions
): TransformContext {
expressionPlugins,
scopeId,
ssr,
+ bindingMetadata,
onError,
// state
// - 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
import {
isGloballyWhitelisted,
makeMap,
- babelParserDefautPlugins
+ babelParserDefautPlugins,
+ hasOwn
} from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
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.
!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
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') {
// 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
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'
babelParserPlugins?: ParserPlugin[]
}
-export interface BindingMetadata {
- [key: string]: 'data' | 'props' | 'setup' | 'ctx'
-}
-
let hasWarned = false
/**
SFCAsyncStyleCompileOptions,
SFCStyleCompileResults
} from './compileStyle'
-export { SFCScriptCompileOptions, BindingMetadata } from './compileScript'
+export { SFCScriptCompileOptions } from './compileScript'
export {
CompilerOptions,
CompilerError,
+ BindingMetadata,
generateCodeFrame
} from '@vue/compiler-core'
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
__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>
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
}
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
/**
slots,
attrs,
emit,
- renderCache
+ render,
+ renderCache,
+ data,
+ setupState,
+ ctx
} = instance
let result
// 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 {
// 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)