Skip to content

Commit 1e35a86

Browse files
committed
refactor: adjust component options merge cache strategy
BREAKING CHANGE: optionMergeStrategies functions no longer receive the component instance as the 3rd argument. The argument was technically internal in Vue 2 and only used for generating warnings, and should not be needed in userland code. This removal enables much more efficient caching of option merging.
1 parent 44996d1 commit 1e35a86

File tree

4 files changed

+86
-43
lines changed

4 files changed

+86
-43
lines changed

packages/runtime-core/src/apiCreateApp.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,7 @@ export interface App<HostElement = any> {
5353
_createRoot?(options: ComponentOptions): ComponentPublicInstance
5454
}
5555

56-
export type OptionMergeFunction = (
57-
to: unknown,
58-
from: unknown,
59-
instance: any,
60-
key: string
61-
) => any
56+
export type OptionMergeFunction = (to: unknown, from: unknown) => any
6257

6358
export interface AppConfig {
6459
// @private
@@ -97,6 +92,13 @@ export interface AppContext {
9792
components: Record<string, Component>
9893
directives: Record<string, Directive>
9994
provides: Record<string | symbol, any>
95+
96+
/**
97+
* Cache for merged/normalized component options
98+
* Each app instance has its own cache because app-level global mixins and
99+
* optionMergeStrategies can affect merge behavior.
100+
*/
101+
cache: WeakMap<ComponentOptions, ComponentOptions>
100102
/**
101103
* Flag for de-optimizing props normalization
102104
* @internal
@@ -137,7 +139,8 @@ export function createAppContext(): AppContext {
137139
mixins: [],
138140
components: {},
139141
directives: {},
140-
provides: Object.create(null)
142+
provides: Object.create(null),
143+
cache: new WeakMap()
141144
}
142145
}
143146

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

-11
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import {
3535
legacyresolveScopedSlots
3636
} from './renderHelpers'
3737
import { resolveFilter } from '../helpers/resolveAssets'
38-
import { resolveMergedOptions } from '../componentOptions'
3938
import { InternalSlots, Slots } from '../componentSlots'
4039
import { ContextualRenderFn } from '../componentRenderContext'
4140

@@ -128,16 +127,6 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
128127
// needed by many libs / render fns
129128
$vnode: i => i.vnode,
130129

131-
// inject addtional properties into $options for compat
132-
// e.g. vuex needs this.$options.parent
133-
$options: i => {
134-
let res = resolveMergedOptions(i)
135-
if (res === i.type) res = i.type.__merged = extend({}, res)
136-
res.parent = i.proxy!.$parent
137-
res.propsData = i.vnode.props
138-
return res
139-
},
140-
141130
// some private properties that are likely accessed...
142131
_self: i => i.proxy,
143132
_uid: i => i.uid,

packages/runtime-core/src/componentOptions.ts

+73-23
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import {
7979
DIRECTIVES,
8080
FILTERS
8181
} from './helpers/resolveAssets'
82+
import { OptionMergeFunction } from './apiCreateApp'
8283

8384
/**
8485
* Interface for declaring custom options.
@@ -194,11 +195,6 @@ export interface ComponentOptionsBase<
194195
* @internal
195196
*/
196197
__asyncResolved?: ConcreteComponent
197-
/**
198-
* cache for merged $options
199-
* @internal
200-
*/
201-
__merged?: ComponentOptions
202198

203199
// Type differentiators ------------------------------------------------------
204200

@@ -486,6 +482,28 @@ interface LegacyOptions<
486482
__differentiator?: keyof D | keyof C | keyof M
487483
}
488484

485+
type MergedHook<T = (() => void)> = T | T[]
486+
487+
export type MergedComponentOptionsOverride = {
488+
beforeCreate?: MergedHook
489+
created?: MergedHook
490+
beforeMount?: MergedHook
491+
mounted?: MergedHook
492+
beforeUpdate?: MergedHook
493+
updated?: MergedHook
494+
activated?: MergedHook
495+
deactivated?: MergedHook
496+
/** @deprecated use `beforeUnmount` instead */
497+
beforeDestroy?: MergedHook
498+
beforeUnmount?: MergedHook
499+
/** @deprecated use `unmounted` instead */
500+
destroyed?: MergedHook
501+
unmounted?: MergedHook
502+
renderTracked?: MergedHook<DebuggerHook>
503+
renderTriggered?: MergedHook<DebuggerHook>
504+
errorCaptured?: MergedHook<ErrorCapturedHook>
505+
}
506+
489507
export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults'
490508

491509
export type OptionTypesType<
@@ -1022,41 +1040,73 @@ export function createWatcher(
10221040
}
10231041
}
10241042

1043+
/**
1044+
* Resolve merged options and cache it on the component.
1045+
* This is done only once per-component since the merging does not involve
1046+
* instances.
1047+
*/
10251048
export function resolveMergedOptions(
10261049
instance: ComponentInternalInstance
1027-
): ComponentOptions {
1028-
const raw = instance.type as ComponentOptions
1029-
const { __merged, mixins, extends: extendsOptions } = raw
1030-
if (__merged) return __merged
1031-
const globalMixins = instance.appContext.mixins
1032-
if (!globalMixins.length && !mixins && !extendsOptions) return raw
1033-
const options = {}
1034-
globalMixins.forEach(m => mergeOptions(options, m, instance))
1035-
mergeOptions(options, raw, instance)
1036-
return (raw.__merged = options)
1050+
): ComponentOptions & MergedComponentOptionsOverride {
1051+
const base = instance.type as ComponentOptions
1052+
const { mixins, extends: extendsOptions } = base
1053+
const {
1054+
mixins: globalMixins,
1055+
cache,
1056+
config: { optionMergeStrategies }
1057+
} = instance.appContext
1058+
const cached = cache.get(base)
1059+
1060+
let resolved: ComponentOptions
1061+
1062+
if (cached) {
1063+
resolved = cached
1064+
} else if (!globalMixins.length && !mixins && !extendsOptions) {
1065+
if (
1066+
__COMPAT__ &&
1067+
isCompatEnabled(DeprecationTypes.PRIVATE_APIS, instance)
1068+
) {
1069+
resolved = extend({}, base)
1070+
resolved.parent = instance.parent && instance.parent.proxy
1071+
resolved.propsData = instance.vnode.props
1072+
} else {
1073+
resolved = base
1074+
}
1075+
} else {
1076+
resolved = {}
1077+
if (globalMixins.length) {
1078+
globalMixins.forEach(m =>
1079+
mergeOptions(resolved, m, optionMergeStrategies)
1080+
)
1081+
}
1082+
mergeOptions(resolved, base, optionMergeStrategies)
1083+
}
1084+
1085+
cache.set(base, resolved)
1086+
return resolved
10371087
}
10381088

10391089
export function mergeOptions(
10401090
to: any,
10411091
from: any,
1042-
instance?: ComponentInternalInstance | null,
1043-
strats = instance && instance.appContext.config.optionMergeStrategies
1092+
strats: Record<string, OptionMergeFunction>
10441093
) {
10451094
if (__COMPAT__ && isFunction(from)) {
10461095
from = from.options
10471096
}
10481097

10491098
const { mixins, extends: extendsOptions } = from
10501099

1051-
extendsOptions && mergeOptions(to, extendsOptions, instance, strats)
1052-
mixins &&
1053-
mixins.forEach((m: ComponentOptionsMixin) =>
1054-
mergeOptions(to, m, instance, strats)
1055-
)
1100+
if (extendsOptions) {
1101+
mergeOptions(to, extendsOptions, strats)
1102+
}
1103+
if (mixins) {
1104+
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, strats))
1105+
}
10561106

10571107
for (const key in from) {
10581108
if (strats && hasOwn(strats, key)) {
1059-
to[key] = strats[key](to[key], from[key], instance && instance.proxy, key)
1109+
to[key] = strats[key](to[key], from[key])
10601110
} else {
10611111
to[key] = from[key]
10621112
}

packages/runtime-core/src/componentPublicInstance.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import {
3333
OptionTypesType,
3434
OptionTypesKeys,
3535
resolveMergedOptions,
36-
shouldCacheAccess
36+
shouldCacheAccess,
37+
MergedComponentOptionsOverride
3738
} from './componentOptions'
3839
import { EmitsOptions, EmitFn } from './componentEmits'
3940
import { Slots } from './componentSlots'
@@ -188,7 +189,7 @@ export type ComponentPublicInstance<
188189
$parent: ComponentPublicInstance | null
189190
$emit: EmitFn<E>
190191
$el: any
191-
$options: Options
192+
$options: Options & MergedComponentOptionsOverride
192193
$forceUpdate: ReactiveEffect
193194
$nextTick: typeof nextTick
194195
$watch(

0 commit comments

Comments
 (0)