]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): support `getCurrentInstance` across mutiple builds of Vue
authorEvan You <yyx990803@gmail.com>
Tue, 21 Feb 2023 13:59:41 +0000 (21:59 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 21 Feb 2023 13:59:41 +0000 (21:59 +0800)
packages/runtime-core/src/component.ts

index 768a47d9b6c6a22419bb65fac796766ab2e64d5a..c75c185035e7a32bc913d817c3c37e96c5ae8af2 100644 (file)
@@ -56,7 +56,8 @@ import {
   makeMap,
   isPromise,
   ShapeFlags,
-  extend
+  extend,
+  getGlobalThis
 } from '@vue/shared'
 import { SuspenseBoundary } from './components/Suspense'
 import { CompilerOptions } from '@vue/compiler-core'
@@ -565,14 +566,73 @@ export let currentInstance: ComponentInternalInstance | null = null
 export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
   currentInstance || currentRenderingInstance
 
+type GlobalInstanceSetter = ((
+  instance: ComponentInternalInstance | null
+) => void) & { version?: string }
+
+let globalCurrentInstanceSetters: GlobalInstanceSetter[]
+let internalSetCurrentInstance: GlobalInstanceSetter
+let hasWarnedDuplicatedVue = false
+
+/**
+ * The following makes getCurrentInstance() usage across multiple copies of Vue
+ * work. Some cases of how this can happen are summarized in #7590. In principle
+ * the duplication should be avoided, but in practice there are often cases
+ * where the user is unable to resolve on their own, especially in complicated
+ * SSR setups.
+ *
+ * Note this fix is technically incomplete, as we still rely on other singletons
+ * for effectScope and global reactive dependency maps. However, it does make
+ * some of the most common cases work. It also warns if the duplication is
+ * found during browser execution.
+ */
+if (__SSR__) {
+  const settersKey = '__VUE_INSTANCE_SETTERS__'
+  if (!(globalCurrentInstanceSetters = getGlobalThis()[settersKey])) {
+    globalCurrentInstanceSetters = getGlobalThis()[settersKey] = []
+  }
+  globalCurrentInstanceSetters.push(i => (currentInstance = i))
+
+  if (__DEV__) {
+    globalCurrentInstanceSetters[
+      globalCurrentInstanceSetters.length - 1
+    ].version = __VERSION__
+  }
+
+  internalSetCurrentInstance = instance => {
+    if (globalCurrentInstanceSetters.length > 1) {
+      // eslint-disable-next-line no-restricted-globals
+      if (__DEV__ && !hasWarnedDuplicatedVue && typeof window !== 'undefined') {
+        warn(
+          `Mixed usage of duplicated Vue runtimes detected: ${globalCurrentInstanceSetters
+            .map(fn => fn.version)
+            .join(', ')}.\n` +
+            `This likely means there are multiple versions of Vue ` +
+            `duplicated in your dependency tree, and could lead to errors. ` +
+            `To avoid this warning, ensure that the all imports of Vue are resolving to ` +
+            `the same location on disk.`
+        )
+        hasWarnedDuplicatedVue = true
+      }
+      globalCurrentInstanceSetters.forEach(s => s(instance))
+    } else {
+      globalCurrentInstanceSetters[0](instance)
+    }
+  }
+} else {
+  internalSetCurrentInstance = i => {
+    currentInstance = i
+  }
+}
+
 export const setCurrentInstance = (instance: ComponentInternalInstance) => {
-  currentInstance = instance
+  internalSetCurrentInstance(instance)
   instance.scope.on()
 }
 
 export const unsetCurrentInstance = () => {
   currentInstance && currentInstance.scope.off()
-  currentInstance = null
+  internalSetCurrentInstance(null)
 }
 
 const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')