Skip to content

Commit

Permalink
fix(runtime-core): align option merge behavior with Vue 2
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 2, 2021
1 parent 1e35a86 commit e2ca67b
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 371 deletions.
182 changes: 182 additions & 0 deletions packages/runtime-core/__tests__/apiOptions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,188 @@ describe('api: options', () => {
)
})

describe('options merge strategies', () => {
test('this.$options.data', () => {
const mixin = {
data() {
return { foo: 1, bar: 2 }
}
}
createApp({
mixins: [mixin],
data() {
return {
foo: 3,
baz: 4
}
},
created() {
expect(this.$options.data).toBeInstanceOf(Function)
expect(this.$options.data()).toEqual({
foo: 3,
bar: 2,
baz: 4
})
},
render: () => null
}).mount(nodeOps.createElement('div'))
})

test('this.$options.inject', () => {
const mixin = {
inject: ['a']
}
const app = createApp({
mixins: [mixin],
inject: { b: 'b', c: { from: 'd' } },
created() {
expect(this.$options.inject.a).toEqual('a')
expect(this.$options.inject.b).toEqual('b')
expect(this.$options.inject.c).toEqual({ from: 'd' })
expect(this.a).toBe(1)
expect(this.b).toBe(2)
expect(this.c).toBe(3)
},
render: () => null
})

app.provide('a', 1)
app.provide('b', 2)
app.provide('d', 3)
app.mount(nodeOps.createElement('div'))
})

test('this.$options.provide', () => {
const mixin = {
provide: {
a: 1
}
}
createApp({
mixins: [mixin],
provide() {
return {
b: 2
}
},
created() {
expect(this.$options.provide).toBeInstanceOf(Function)
expect(this.$options.provide()).toEqual({ a: 1, b: 2 })
},
render: () => null
}).mount(nodeOps.createElement('div'))
})

test('this.$options[lifecycle-name]', () => {
const mixin = {
mounted() {}
}
createApp({
mixins: [mixin],
mounted() {},
created() {
expect(this.$options.mounted).toBeInstanceOf(Array)
expect(this.$options.mounted.length).toBe(2)
},
render: () => null
}).mount(nodeOps.createElement('div'))
})

test('this.$options[asset-name]', () => {
const mixin = {
components: {
a: {}
},
directives: {
d1: {}
}
}
createApp({
mixins: [mixin],
components: {
b: {}
},
directives: {
d2: {}
},
created() {
expect('a' in this.$options.components).toBe(true)
expect('b' in this.$options.components).toBe(true)
expect('d1' in this.$options.directives).toBe(true)
expect('d2' in this.$options.directives).toBe(true)
},
render: () => null
}).mount(nodeOps.createElement('div'))
})

test('this.$options.methods', () => {
const mixin = {
methods: {
fn1() {}
}
}
createApp({
mixins: [mixin],
methods: {
fn2() {}
},
created() {
expect(this.$options.methods.fn1).toBeInstanceOf(Function)
expect(this.$options.methods.fn2).toBeInstanceOf(Function)
},
render: () => null
}).mount(nodeOps.createElement('div'))
})

test('this.$options.computed', () => {
const mixin = {
computed: {
c1() {}
}
}
createApp({
mixins: [mixin],
computed: {
c2() {}
},
created() {
expect(this.$options.computed.c1).toBeInstanceOf(Function)
expect(this.$options.computed.c2).toBeInstanceOf(Function)
},
render: () => null
}).mount(nodeOps.createElement('div'))
})

// #2791
test('modify $options in the beforeCreate hook', async () => {
const count = ref(0)
const mixin = {
data() {
return { foo: 1 }
},
beforeCreate(this: any) {
if (!this.$options.computed) {
this.$options.computed = {}
}
this.$options.computed.value = () => count.value
}
}
const root = nodeOps.createElement('div')
createApp({
mixins: [mixin],
render(this: any) {
return this.value
}
}).mount(root)

expect(serializeInner(root)).toBe('0')

count.value++
await nextTick()
expect(serializeInner(root)).toBe('1')
})
})

describe('warnings', () => {
test('Expected a function as watch handler', () => {
const Comp = {
Expand Down
8 changes: 6 additions & 2 deletions packages/runtime-core/src/apiCreateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {
validateComponentName,
Component
} from './component'
import { ComponentOptions, RuntimeCompilerOptions } from './componentOptions'
import {
ComponentOptions,
MergedComponentOptions,
RuntimeCompilerOptions
} from './componentOptions'
import { ComponentPublicInstance } from './componentPublicInstance'
import { Directive, validateDirectiveName } from './directives'
import { RootRenderFunction } from './renderer'
Expand Down Expand Up @@ -98,7 +102,7 @@ export interface AppContext {
* Each app instance has its own cache because app-level global mixins and
* optionMergeStrategies can affect merge behavior.
*/
cache: WeakMap<ComponentOptions, ComponentOptions>
cache: WeakMap<ComponentOptions, MergedComponentOptions>
/**
* Flag for de-optimizing props normalization
* @internal
Expand Down
13 changes: 12 additions & 1 deletion packages/runtime-core/src/compat/compatConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,10 @@ const seenConfigObjects = /*#__PURE__*/ new WeakSet<CompatConfig>()
const warnedInvalidKeys: Record<string, boolean> = {}

// dev only
export function validateCompatConfig(config: CompatConfig) {
export function validateCompatConfig(
config: CompatConfig,
instance?: ComponentInternalInstance
) {
if (seenConfigObjects.has(config)) {
return
}
Expand All @@ -558,6 +561,14 @@ export function validateCompatConfig(config: CompatConfig) {
warnedInvalidKeys[key] = true
}
}

if (instance && config[DeprecationTypes.OPTIONS_DATA_MERGE] != null) {
warn(
`Deprecation config "${
DeprecationTypes.OPTIONS_DATA_MERGE
}" can only be configured globally.`
)
}
}

export function getCompatConfigForKey(
Expand Down
31 changes: 4 additions & 27 deletions packages/runtime-core/src/compat/data.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
import { isFunction, isPlainObject } from '@vue/shared'
import { ComponentInternalInstance } from '../component'
import { ComponentPublicInstance } from '../componentPublicInstance'
import { isPlainObject } from '@vue/shared'
import { DeprecationTypes, warnDeprecation } from './compatConfig'

export function deepMergeData(
to: any,
from: any,
instance: ComponentInternalInstance
) {
export function deepMergeData(to: any, from: any) {
for (const key in from) {
const toVal = to[key]
const fromVal = from[key]
if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) {
__DEV__ &&
warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, instance, key)
deepMergeData(toVal, fromVal, instance)
__DEV__ && warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, null, key)
deepMergeData(toVal, fromVal)
} else {
to[key] = fromVal
}
}
return to
}

export function mergeDataOption(to: any, from: any) {
if (!from) {
return to
}
if (!to) {
return from
}
return function mergedDataFn(this: ComponentPublicInstance) {
return deepMergeData(
isFunction(to) ? to.call(this, this) : to,
isFunction(from) ? from.call(this, this) : from,
this.$
)
}
}
18 changes: 9 additions & 9 deletions packages/runtime-core/src/compat/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ import {
isRuntimeOnly,
setupComponent
} from '../component'
import { RenderFunction, mergeOptions } from '../componentOptions'
import {
RenderFunction,
mergeOptions,
internalOptionMergeStrats
} from '../componentOptions'
import { ComponentPublicInstance } from '../componentPublicInstance'
import { devtoolsInitApp, devtoolsUnmountApp } from '../devtools'
import { Directive } from '../directives'
Expand All @@ -43,8 +47,7 @@ import { version } from '..'
import {
installLegacyConfigWarnings,
installLegacyOptionMergeStrats,
LegacyConfig,
legacyOptionMergeStrats
LegacyConfig
} from './globalConfig'
import { LegacyDirective } from './customDirective'
import {
Expand Down Expand Up @@ -231,8 +234,7 @@ export function createCompatVue(
mergeOptions(
extend({}, SubVue.options),
inlineOptions,
null,
legacyOptionMergeStrats as any
internalOptionMergeStrats as any
),
SubVue
)
Expand All @@ -257,8 +259,7 @@ export function createCompatVue(
SubVue.options = mergeOptions(
mergeBase,
extendOptions,
null,
legacyOptionMergeStrats as any
internalOptionMergeStrats as any
)

SubVue.options._base = SubVue
Expand Down Expand Up @@ -305,8 +306,7 @@ export function createCompatVue(
mergeOptions(
parent,
child,
vm && vm.$,
vm ? undefined : (legacyOptionMergeStrats as any)
vm ? undefined : (internalOptionMergeStrats as any)
),
defineReactive
}
Expand Down
Loading

0 comments on commit e2ca67b

Please sign in to comment.