Răsfoiți Sursa

wip: make singleton mutations affect all app instances

Evan You 5 ani în urmă
părinte
comite
f2a5a3ee55

+ 2 - 1
packages/runtime-core/src/apiCreateApp.ts

@@ -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

+ 74 - 52
packages/runtime-core/src/compat/global.ts

@@ -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,

+ 20 - 0
packages/vue-compat/__tests__/global.spec.ts

@@ -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
+})

+ 10 - 0
packages/vue-compat/__tests__/globalConfig.spec.ts

@@ -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>`)
+})

+ 1 - 3
packages/vue-compat/src/createCompatVue.ts

@@ -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
 }