Skip to content

Commit d58d133

Browse files
committed
fix(compat): fix $options mutation + adjust private API initialization
close #10626 close #10636
1 parent 04af950 commit d58d133

File tree

3 files changed

+119
-43
lines changed

3 files changed

+119
-43
lines changed

packages/runtime-core/src/compat/instance.ts

+71-43
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
DeprecationTypes,
1616
assertCompatEnabled,
1717
isCompatEnabled,
18+
warnDeprecation,
1819
} from './compatConfig'
1920
import { off, on, once } from './instanceEventEmitter'
2021
import { getCompatListeners } from './instanceListeners'
@@ -121,50 +122,77 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
121122

122123
$children: getCompatChildren,
123124
$listeners: getCompatListeners,
125+
126+
// inject additional properties into $options for compat
127+
// e.g. vuex needs this.$options.parent
128+
$options: i => {
129+
if (!isCompatEnabled(DeprecationTypes.PRIVATE_APIS, i)) {
130+
return resolveMergedOptions(i)
131+
}
132+
if (i.resolvedOptions) {
133+
return i.resolvedOptions
134+
}
135+
const res = (i.resolvedOptions = extend({}, resolveMergedOptions(i)))
136+
Object.defineProperties(res, {
137+
parent: {
138+
get() {
139+
warnDeprecation(DeprecationTypes.PRIVATE_APIS, i, '$options.parent')
140+
return i.proxy!.$parent
141+
},
142+
},
143+
propsData: {
144+
get() {
145+
warnDeprecation(
146+
DeprecationTypes.PRIVATE_APIS,
147+
i,
148+
'$options.propsData',
149+
)
150+
return i.vnode.props
151+
},
152+
},
153+
})
154+
return res
155+
},
124156
} as PublicPropertiesMap)
125157

126-
/* istanbul ignore if */
127-
if (isCompatEnabled(DeprecationTypes.PRIVATE_APIS, null)) {
128-
extend(map, {
129-
// needed by many libs / render fns
130-
$vnode: i => i.vnode,
131-
132-
// inject additional properties into $options for compat
133-
// e.g. vuex needs this.$options.parent
134-
$options: i => {
135-
const res = extend({}, resolveMergedOptions(i))
136-
res.parent = i.proxy!.$parent
137-
res.propsData = i.vnode.props
138-
return res
139-
},
140-
141-
// some private properties that are likely accessed...
142-
_self: i => i.proxy,
143-
_uid: i => i.uid,
144-
_data: i => i.data,
145-
_isMounted: i => i.isMounted,
146-
_isDestroyed: i => i.isUnmounted,
147-
148-
// v2 render helpers
149-
$createElement: () => compatH,
150-
_c: () => compatH,
151-
_o: () => legacyMarkOnce,
152-
_n: () => looseToNumber,
153-
_s: () => toDisplayString,
154-
_l: () => renderList,
155-
_t: i => legacyRenderSlot.bind(null, i),
156-
_q: () => looseEqual,
157-
_i: () => looseIndexOf,
158-
_m: i => legacyRenderStatic.bind(null, i),
159-
_f: () => resolveFilter,
160-
_k: i => legacyCheckKeyCodes.bind(null, i),
161-
_b: () => legacyBindObjectProps,
162-
_v: () => createTextVNode,
163-
_e: () => createCommentVNode,
164-
_u: () => legacyresolveScopedSlots,
165-
_g: () => legacyBindObjectListeners,
166-
_d: () => legacyBindDynamicKeys,
167-
_p: () => legacyPrependModifier,
168-
} as PublicPropertiesMap)
158+
const privateAPIs = {
159+
// needed by many libs / render fns
160+
$vnode: i => i.vnode,
161+
162+
// some private properties that are likely accessed...
163+
_self: i => i.proxy,
164+
_uid: i => i.uid,
165+
_data: i => i.data,
166+
_isMounted: i => i.isMounted,
167+
_isDestroyed: i => i.isUnmounted,
168+
169+
// v2 render helpers
170+
$createElement: () => compatH,
171+
_c: () => compatH,
172+
_o: () => legacyMarkOnce,
173+
_n: () => looseToNumber,
174+
_s: () => toDisplayString,
175+
_l: () => renderList,
176+
_t: i => legacyRenderSlot.bind(null, i),
177+
_q: () => looseEqual,
178+
_i: () => looseIndexOf,
179+
_m: i => legacyRenderStatic.bind(null, i),
180+
_f: () => resolveFilter,
181+
_k: i => legacyCheckKeyCodes.bind(null, i),
182+
_b: () => legacyBindObjectProps,
183+
_v: () => createTextVNode,
184+
_e: () => createCommentVNode,
185+
_u: () => legacyresolveScopedSlots,
186+
_g: () => legacyBindObjectListeners,
187+
_d: () => legacyBindDynamicKeys,
188+
_p: () => legacyPrependModifier,
189+
} as PublicPropertiesMap
190+
191+
for (const key in privateAPIs) {
192+
map[key] = i => {
193+
if (isCompatEnabled(DeprecationTypes.PRIVATE_APIS, i)) {
194+
return privateAPIs[key](i)
195+
}
196+
}
169197
}
170198
}

packages/runtime-core/src/component.ts

+7
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { type Directive, validateDirectiveName } from './directives'
4545
import {
4646
type ComponentOptions,
4747
type ComputedOptions,
48+
type MergedComponentOptions,
4849
type MethodOptions,
4950
applyOptions,
5051
resolveMergedOptions,
@@ -524,6 +525,12 @@ export interface ComponentInternalInstance {
524525
* @internal
525526
*/
526527
getCssVars?: () => Record<string, string>
528+
529+
/**
530+
* v2 compat only, for caching mutated $options
531+
* @internal
532+
*/
533+
resolvedOptions?: MergedComponentOptions
527534
}
528535

529536
const emptyAppContext = createAppContext()

packages/vue-compat/__tests__/instance.spec.ts

+41
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ beforeEach(() => {
1414
Vue.configureCompat({
1515
MODE: 2,
1616
GLOBAL_MOUNT: 'suppress-warning',
17+
PRIVATE_APIS: 'suppress-warning',
1718
})
1819
})
1920

@@ -331,3 +332,43 @@ test('INSTANCE_ATTR_CLASS_STYLE', () => {
331332
)('Anonymous'),
332333
).toHaveBeenWarned()
333334
})
335+
336+
test('$options mutation', () => {
337+
const Comp = {
338+
props: ['id'],
339+
template: '<div/>',
340+
data() {
341+
return {
342+
foo: '',
343+
}
344+
},
345+
created(this: any) {
346+
expect(this.$options.parent).toBeDefined()
347+
expect(this.$options.test).toBeUndefined()
348+
this.$options.test = this.id
349+
expect(this.$options.test).toBe(this.id)
350+
},
351+
}
352+
353+
new Vue({
354+
template: `<div><Comp id="1"/><Comp id="2"/></div>`,
355+
components: { Comp },
356+
}).$mount()
357+
})
358+
359+
test('other private APIs', () => {
360+
new Vue({
361+
created() {
362+
expect(this.$createElement).toBeTruthy()
363+
},
364+
})
365+
366+
new Vue({
367+
compatConfig: {
368+
PRIVATE_APIS: false,
369+
},
370+
created() {
371+
expect(this.$createElement).toBeUndefined()
372+
},
373+
})
374+
})

0 commit comments

Comments
 (0)