const handler = (app.config.warnHandler = jest.fn(
(msg, instance, trace) => {
expect(msg).toMatch(`Component is missing template or render function`)
- expect(instance).toBe(ctx.renderProxy)
+ expect(instance).toBe(ctx.proxy)
expect(trace).toMatch(`Hello`)
}
))
describe('component: proxy', () => {
mockWarn()
- it('data', () => {
+ test('data', () => {
const app = createApp()
let instance: ComponentInternalInstance
let instanceProxy: any
expect(instance!.data.foo).toBe(2)
})
- it('renderContext', () => {
+ test('renderContext', () => {
const app = createApp()
let instance: ComponentInternalInstance
let instanceProxy: any
expect(instance!.renderContext.foo).toBe(2)
})
- it('propsProxy', () => {
+ test('propsProxy', () => {
const app = createApp()
let instance: ComponentInternalInstance
let instanceProxy: any
expect(`Attempting to mutate prop "foo"`).toHaveBeenWarned()
})
- it('methods', () => {
+ test('public properties', () => {
const app = createApp()
let instance: ComponentInternalInstance
let instanceProxy: any
expect(`Attempting to mutate public property "$data"`).toHaveBeenWarned()
})
- it('sink', async () => {
+ test('sink', async () => {
const app = createApp()
let instance: ComponentInternalInstance
let instanceProxy: any
expect(instanceProxy.foo).toBe(1)
expect(instance!.sink.foo).toBe(1)
})
+
+ test('has check', () => {
+ const app = createApp()
+ let instanceProxy: any
+ const Comp = {
+ render() {},
+ props: {
+ msg: String
+ },
+ data() {
+ return {
+ foo: 0
+ }
+ },
+ setup() {
+ return {
+ bar: 1
+ }
+ },
+ mounted() {
+ instanceProxy = this
+ }
+ }
+ app.mount(Comp, nodeOps.createElement('div'), { msg: 'hello' })
+
+ // props
+ expect('msg' in instanceProxy).toBe(true)
+ // data
+ expect('foo' in instanceProxy).toBe(true)
+ // renderContext
+ expect('bar' in instanceProxy).toBe(true)
+ // public properties
+ expect('$el' in instanceProxy).toBe(true)
+
+ // non-existent
+ expect('$foobar' in instanceProxy).toBe(false)
+ expect('baz' in instanceProxy).toBe(false)
+
+ // set non-existent (goes into sink)
+ instanceProxy.baz = 1
+ expect('baz' in instanceProxy).toBe(true)
+ })
})
function assertBindings(binding: DirectiveBinding) {
expect(binding.value).toBe(count.value)
expect(binding.arg).toBe('foo')
- expect(binding.instance).toBe(_instance && _instance.renderProxy)
+ expect(binding.instance).toBe(_instance && _instance.proxy)
expect(binding.modifiers && binding.modifiers.ok).toBe(true)
}
function assertBindings(binding: DirectiveBinding) {
expect(binding.value).toBe(count.value)
expect(binding.arg).toBe('foo')
- expect(binding.instance).toBe(_instance && _instance.renderProxy)
+ expect(binding.instance).toBe(_instance && _instance.proxy)
expect(binding.modifiers && binding.modifiers.ok).toBe(true)
}
vnode.appContext = context
render(vnode, rootContainer)
isMounted = true
- return vnode.component!.renderProxy
+ return vnode.component!.proxy
} else if (__DEV__) {
warn(
`App has already been mounted. Create a new app instance instead.`
instance.renderContext === EMPTY_OBJ
? (instance.renderContext = reactive({}))
: instance.renderContext
- const ctx = instance.renderProxy!
+ const ctx = instance.proxy!
const {
// composition
mixins,
cb: Function,
options?: WatchOptions
): StopHandle {
- const ctx = this.renderProxy as Data
+ const ctx = this.proxy as Data
const getter = isString(source) ? () => ctx[source] : source.bind(ctx)
const stop = watch(getter, cb.bind(ctx), options)
onBeforeUnmount(stop, this)
import { ReactiveEffect, reactive, shallowReadonly } from '@vue/reactivity'
import {
PublicInstanceProxyHandlers,
- ComponentPublicInstance
+ ComponentPublicInstance,
+ runtimeCompiledRenderProxyHandlers
} from './componentProxy'
import { ComponentPropsOptions } from './componentProps'
import { Slots } from './componentSlots'
emit: Emit
}
-export type RenderFunction = () => VNodeChild
+export type RenderFunction = {
+ (): VNodeChild
+ isRuntimeCompiled?: boolean
+}
export interface ComponentInternalInstance {
type: FunctionalComponent | ComponentOptions
render: RenderFunction | null
effects: ReactiveEffect[] | null
provides: Data
- // cache for renderProxy access type to avoid hasOwnProperty calls
+ // cache for proxy access type to avoid hasOwnProperty calls
accessCache: Data | null
// cache for render function values that rely on _ctx but won't need updates
// after initialized (e.g. inline handlers)
props: Data
attrs: Data
slots: Slots
- renderProxy: ComponentPublicInstance | null
+ proxy: ComponentPublicInstance | null
+ // alternative proxy used only for runtime-compiled render functions using
+ // `with` block
+ withProxy: ComponentPublicInstance | null
propsProxy: Data | null
setupContext: SetupContext | null
refs: Data
subTree: null!, // will be set synchronously right after creation
update: null!, // will be set synchronously right after creation
render: null,
- renderProxy: null,
+ proxy: null,
+ withProxy: null,
propsProxy: null,
setupContext: null,
effects: null,
}
// 0. create render proxy property access cache
instance.accessCache = {}
- // 1. create render proxy
- instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers)
+ // 1. create public instance / render proxy
+ instance.proxy = new Proxy(instance, PublicInstanceProxyHandlers)
// 2. create props proxy
// the propsProxy is a reactive AND readonly proxy to the actual props.
// it will be updated in resolveProps() on updates before render
}
})
}
+
if (__DEV__ && !Component.render) {
/* istanbul ignore if */
if (!__RUNTIME_COMPILE__ && Component.template) {
)
}
}
+
instance.render = (Component.render || NOOP) as RenderFunction
+
+ // for runtime-compiled render functions using `with` blocks, the render
+ // proxy used needs a different `has` handler which is more performant and
+ // also only allows a whitelist of globals to fallthrough.
+ if (__RUNTIME_COMPILE__ && instance.render.isRuntimeCompiled) {
+ instance.withProxy = new Proxy(
+ instance,
+ runtimeCompiledRenderProxyHandlers
+ )
+ }
}
// support for 2.x options
ExtractComputedReturns<C> &
M
-const publicPropertiesMap = {
- $data: 'data',
- $props: 'propsProxy',
- $attrs: 'attrs',
- $slots: 'slots',
- $refs: 'refs',
- $parent: 'parent',
- $root: 'root',
- $emit: 'emit',
- $options: 'type'
+const publicPropertiesMap: Record<
+ string,
+ (i: ComponentInternalInstance) => any
+> = {
+ $: i => i,
+ $el: i => i.vnode.el,
+ $cache: i => i.renderCache,
+ $data: i => i.data,
+ $props: i => i.propsProxy,
+ $attrs: i => i.attrs,
+ $slots: i => i.slots,
+ $refs: i => i.refs,
+ $parent: i => i.parent,
+ $root: i => i.root,
+ $emit: i => i.emit,
+ $options: i => i.type,
+ $forceUpdate: i => i.update,
+ $nextTick: () => nextTick,
+ $watch: i => instanceWatch.bind(i)
}
const enum AccessTypes {
type,
sink
} = target
+
+ // data / props / renderContext
// This getter gets called for every property access on the render context
// during render and is a major hotspot. The most expensive part of this
// is the multiple hasOwn() calls. It's much faster to do a simple property
}
// return the value from propsProxy for ref unwrapping and readonly
return propsProxy![key]
- } else if (key === '$') {
- // reserved backdoor to access the internal instance
- return target
- } else if (key === '$cache') {
- return target.renderCache || (target.renderCache = [])
- } else if (key === '$el') {
- return target.vnode.el
- } else if (hasOwn(publicPropertiesMap, key)) {
+ }
+
+ // public $xxx properties & user-attached properties (sink)
+ const publicGetter = publicPropertiesMap[key]
+ if (publicGetter !== undefined) {
if (__DEV__ && key === '$attrs') {
markAttrsAccessed()
}
- return target[publicPropertiesMap[key]]
- }
- // methods are only exposed when options are supported
- if (__FEATURE_OPTIONS__) {
- switch (key) {
- case '$forceUpdate':
- return target.update
- case '$nextTick':
- return nextTick
- case '$watch':
- return instanceWatch.bind(target)
- }
- }
- if (hasOwn(sink, key)) {
+ return publicGetter(target)
+ } else if (hasOwn(sink, key)) {
return sink[key]
} else if (__DEV__ && currentRenderingInstance != null) {
warn(
}
},
+ has(target: ComponentInternalInstance, key: string) {
+ const { data, accessCache, renderContext, type, sink } = target
+ return (
+ accessCache![key] !== undefined ||
+ (data !== EMPTY_OBJ && hasOwn(data, key)) ||
+ hasOwn(renderContext, key) ||
+ (type.props != null && hasOwn(type.props, key)) ||
+ hasOwn(publicPropertiesMap, key) ||
+ hasOwn(sink, key)
+ )
+ },
+
set(target: ComponentInternalInstance, key: string, value: any): boolean {
const { data, renderContext } = target
if (data !== EMPTY_OBJ && hasOwn(data, key)) {
}
}
-if (__RUNTIME_COMPILE__) {
- // this trap is only called in browser-compiled render functions that use
- // `with (this) {}`
- PublicInstanceProxyHandlers.has = (
- _: ComponentInternalInstance,
- key: string
- ): boolean => {
+export const runtimeCompiledRenderProxyHandlers = {
+ ...PublicInstanceProxyHandlers,
+ has(_target: ComponentInternalInstance, key: string) {
return key[0] !== '_' && !isGloballyWhitelisted(key)
}
}
const {
type: Component,
vnode,
- renderProxy,
+ proxy,
+ withProxy,
props,
slots,
attrs,
}
try {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
- result = normalizeVNode(instance.render!.call(renderProxy))
+ result = normalizeVNode(instance.render!.call(withProxy || proxy))
} else {
// functional
const render = Component as FunctionalComponent
__DEV__ && warn(`withDirectives can only be used inside render functions.`)
return vnode
}
- const instance = internalInstance.renderProxy
+ const instance = internalInstance.proxy
const props = vnode.props || (vnode.props = {})
const bindings = vnode.dirs || (vnode.dirs = new Array(directives.length))
const injected: Record<string, true> = {}
if (instance) {
let cur = instance.parent
// the exposed instance is the render proxy to keep it consistent with 2.x
- const exposedInstance = instance.renderProxy
+ const exposedInstance = instance.proxy
// in production the hook receives only the error code
const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
while (cur) {
)
popWarningContext()
}
- setRef(n2.ref, n1 && n1.ref, parentComponent, n2.component!.renderProxy)
+ setRef(n2.ref, n1 && n1.ref, parentComponent, n2.component!.proxy)
}
}
ErrorCodes.APP_WARN_HANDLER,
[
msg + args.join(''),
- instance && instance.renderProxy,
+ instance && instance.proxy,
trace
.map(({ vnode }) => `at <${formatComponentName(vnode)}>`)
.join('\n'),
...options
})
- return new Function('Vue', code)(runtimeDom) as RenderFunction
+ const render = new Function('Vue', code)(runtimeDom) as RenderFunction
+ render.isRuntimeCompiled = true
+ return render
}
registerRuntimeCompiler(compileToFunction)