wip: make singleton mutations affect all app instances

This commit is contained in:
Evan You 2021-05-05 17:56:09 -04:00
parent 61edb700d7
commit f2a5a3ee55
5 changed files with 107 additions and 56 deletions

View File

@ -15,7 +15,7 @@ import { RootHydrateFunction } from './hydration'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
import { isFunction, NO, isObject } from '@vue/shared'
import { version } from '.'
import { installCompatMount } from './compat/global'
import { applySingletonAppMutations, installCompatMount } from './compat/global'
import { installLegacyConfigProperties } from './compat/globalConfig'
import { installGlobalFilterMethod } from './compat/filter'
@ -331,6 +331,7 @@ export function createAppAPI<HostElement>(
installCompatMount(app, context, render, hydrate)
installGlobalFilterMethod(app, context)
if (__DEV__) installLegacyConfigProperties(app.config)
applySingletonAppMutations(app)
}
return app

View File

@ -115,15 +115,22 @@ export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
export let isCopyingConfig = false
// exported only for test
export let singletonApp: App
let singletonCtor: Function
// Legacy global Vue constructor
export function createCompatVue(
createApp: CreateAppFunction<Element>
createApp: CreateAppFunction<Element>,
createSingletonApp: CreateAppFunction<Element>
): CompatVue {
const Vue: CompatVue = function Vue(options: ComponentOptions = {}) {
return createCompatApp(options, Vue)
} as any
singletonApp = createSingletonApp({})
const singletonApp = createApp({})
const Vue: CompatVue = (singletonCtor = function Vue(
options: ComponentOptions = {}
) {
return createCompatApp(options, Vue)
} as any)
function createCompatApp(options: ComponentOptions = {}, Ctor: any) {
assertCompatEnabled(DeprecationTypes.GLOBAL_MOUNT, null)
@ -139,53 +146,8 @@ export function createCompatVue(
const app = createApp(options)
// copy over asset registries and deopt flag
;['mixins', 'components', 'directives', 'filters', 'deopt'].forEach(key => {
// @ts-ignore
app._context[key] = singletonApp._context[key]
})
// copy over global config mutations
isCopyingConfig = true
for (const key in singletonApp.config) {
if (key === 'isNativeTag') continue
if (
isRuntimeOnly() &&
(key === 'isCustomElement' || key === 'compilerOptions')
) {
continue
}
const val = singletonApp.config[key as keyof AppConfig]
// @ts-ignore
app.config[key] = val
// compat for runtime ignoredElements -> isCustomElement
if (
key === 'ignoredElements' &&
isCompatEnabled(DeprecationTypes.CONFIG_IGNORED_ELEMENTS, null) &&
!isRuntimeOnly() &&
isArray(val)
) {
app.config.compilerOptions.isCustomElement = tag => {
return val.some(v => (isString(v) ? v === tag : v.test(tag)))
}
}
}
isCopyingConfig = false
// copy prototype augmentations as config.globalProperties
if (isCompatEnabled(DeprecationTypes.GLOBAL_PROTOTYPE, null)) {
app.config.globalProperties = Ctor.prototype
}
let hasPrototypeAugmentations = false
for (const key in Ctor.prototype) {
if (key !== 'constructor') {
hasPrototypeAugmentations = true
break
}
}
if (__DEV__ && hasPrototypeAugmentations) {
warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE, null)
if (Ctor !== Vue) {
applySingletonPrototype(app, Ctor)
}
const vm = app._createRoot!(options)
@ -348,6 +310,66 @@ export function createCompatVue(
return Vue
}
export function applySingletonAppMutations(app: App, Ctor?: Function) {
if (!singletonApp) {
// this is the call of creating the singleton itself
return
}
// copy over asset registries and deopt flag
;['mixins', 'components', 'directives', 'filters', 'deopt'].forEach(key => {
// @ts-ignore
app._context[key] = singletonApp._context[key]
})
// copy over global config mutations
isCopyingConfig = true
for (const key in singletonApp.config) {
if (key === 'isNativeTag') continue
if (
isRuntimeOnly() &&
(key === 'isCustomElement' || key === 'compilerOptions')
) {
continue
}
const val = singletonApp.config[key as keyof AppConfig]
// @ts-ignore
app.config[key] = val
// compat for runtime ignoredElements -> isCustomElement
if (
key === 'ignoredElements' &&
isCompatEnabled(DeprecationTypes.CONFIG_IGNORED_ELEMENTS, null) &&
!isRuntimeOnly() &&
isArray(val)
) {
app.config.compilerOptions.isCustomElement = tag => {
return val.some(v => (isString(v) ? v === tag : v.test(tag)))
}
}
}
isCopyingConfig = false
applySingletonPrototype(app, singletonCtor)
}
function applySingletonPrototype(app: App, Ctor: Function) {
// copy prototype augmentations as config.globalProperties
if (isCompatEnabled(DeprecationTypes.GLOBAL_PROTOTYPE, null)) {
app.config.globalProperties = Ctor.prototype
}
let hasPrototypeAugmentations = false
for (const key in Ctor.prototype) {
if (key !== 'constructor') {
hasPrototypeAugmentations = true
break
}
}
if (__DEV__ && hasPrototypeAugmentations) {
warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE, null)
}
}
export function installCompatMount(
app: App,
context: AppContext,

View File

@ -6,6 +6,8 @@ import {
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/compatConfig'
import { singletonApp } from '../../runtime-core/src/compat/global'
import { createApp } from '../src/esm-index'
beforeEach(() => {
toggleDeprecationWarning(false)
@ -280,6 +282,15 @@ describe('GLOBAL_PROTOTYPE', () => {
const plain = new Vue() as any
expect(plain.$test).toBeUndefined()
})
test('should affect apps created via createApp()', () => {
Vue.prototype.$test = 1
const vm = createApp({
template: 'foo'
}).mount(document.createElement('div')) as any
expect(vm.$test).toBe(1)
delete Vue.prototype.$test
})
})
describe('GLOBAL_SET/DELETE', () => {
@ -381,3 +392,12 @@ describe('GLOBAL_PRIVATE_UTIL', () => {
expect(n).toBe(2)
})
})
test('global asset registration should affect apps created via createApp', () => {
Vue.component('foo', { template: 'foo' })
const vm = createApp({
template: '<foo/>'
}).mount(document.createElement('div')) as any
expect(vm.$el.textContent).toBe('foo')
delete singletonApp._context.components.foo
})

View File

@ -1,5 +1,6 @@
import Vue from '@vue/compat'
import { toggleDeprecationWarning } from '../../runtime-core/src/compat/compatConfig'
import { createApp } from '../src/esm-index'
import { triggerEvent } from './utils'
beforeEach(() => {
@ -64,3 +65,12 @@ test('GLOBAL_IGNORED_ELEMENTS', () => {
})
expect(el.innerHTML).toBe(`<v-foo></v-foo><foo></foo>`)
})
test('singleton config should affect apps created with createApp()', () => {
Vue.config.ignoredElements = [/^v-/, 'foo']
const el = document.createElement('div')
createApp({
template: `<v-foo/><foo/>`
}).mount(el)
expect(el.innerHTML).toBe(`<v-foo></v-foo><foo></foo>`)
})

View File

@ -38,9 +38,7 @@ function wrappedCreateApp(...args: any[]) {
}
export function createCompatVue() {
const Vue = compatUtils.createCompatVue(wrappedCreateApp)
const Vue = compatUtils.createCompatVue(createApp, wrappedCreateApp)
extend(Vue, runtimeDom)
// @ts-ignore
Vue.createApp = wrappedCreateApp
return Vue
}