Przeglądaj źródła

fix(devtools): fix prod devtools detection + handle late devtools hook injection (#4653)

Evan You 4 lat temu
rodzic
commit
2476eaad6e

+ 1 - 1
packages/runtime-core/__tests__/helpers/renderSlot.spec.ts

@@ -47,7 +47,7 @@ describe('renderSlot', () => {
         return [createVNode('div', null, 'foo', PatchFlags.TEXT)]
       },
       // mock instance
-      { type: {} } as any
+      { type: {}, appContext: {} } as any
     ) as Slot
 
     // manual invocation should not track

+ 28 - 19
packages/runtime-core/src/devtools.ts

@@ -21,6 +21,7 @@ const enum DevtoolsHooks {
 }
 
 interface DevtoolsHook {
+  enabled?: boolean
   emit: (event: string, ...payload: any[]) => void
   on: (event: string, handler: Function) => void
   once: (event: string, handler: Function) => void
@@ -30,14 +31,33 @@ interface DevtoolsHook {
 
 export let devtools: DevtoolsHook
 
-export function setDevtoolsHook(hook: DevtoolsHook) {
+let buffer: { event: string; args: any[] }[] = []
+
+function emit(event: string, ...args: any[]) {
+  if (devtools) {
+    devtools.emit(event, ...args)
+  } else {
+    buffer.push({ event, args })
+  }
+}
+
+export function setDevtoolsHook(hook: DevtoolsHook, target: any) {
   devtools = hook
+  if (devtools) {
+    devtools.enabled = true
+    buffer.forEach(({ event, args }) => devtools.emit(event, ...args))
+    buffer = []
+  } else {
+    const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
+      target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [])
+    replay.push((newHook: DevtoolsHook) => {
+      setDevtoolsHook(newHook, target)
+    })
+  }
 }
 
 export function devtoolsInitApp(app: App, version: string) {
-  // TODO queue if devtools is undefined
-  if (!devtools) return
-  devtools.emit(DevtoolsHooks.APP_INIT, app, version, {
+  emit(DevtoolsHooks.APP_INIT, app, version, {
     Fragment,
     Text,
     Comment,
@@ -46,8 +66,7 @@ export function devtoolsInitApp(app: App, version: string) {
 }
 
 export function devtoolsUnmountApp(app: App) {
-  if (!devtools) return
-  devtools.emit(DevtoolsHooks.APP_UNMOUNT, app)
+  emit(DevtoolsHooks.APP_UNMOUNT, app)
 }
 
 export const devtoolsComponentAdded = /*#__PURE__*/ createDevtoolsComponentHook(
@@ -62,8 +81,7 @@ export const devtoolsComponentRemoved =
 
 function createDevtoolsComponentHook(hook: DevtoolsHooks) {
   return (component: ComponentInternalInstance) => {
-    if (!devtools) return
-    devtools.emit(
+    emit(
       hook,
       component.appContext.app,
       component.uid,
@@ -83,15 +101,7 @@ export const devtoolsPerfEnd = /*#__PURE__*/ createDevtoolsPerformanceHook(
 
 function createDevtoolsPerformanceHook(hook: DevtoolsHooks) {
   return (component: ComponentInternalInstance, type: string, time: number) => {
-    if (!devtools) return
-    devtools.emit(
-      hook,
-      component.appContext.app,
-      component.uid,
-      component,
-      type,
-      time
-    )
+    emit(hook, component.appContext.app, component.uid, component, type, time)
   }
 }
 
@@ -100,8 +110,7 @@ export function devtoolsComponentEmit(
   event: string,
   params: any[]
 ) {
-  if (!devtools) return
-  devtools.emit(
+  emit(
     DevtoolsHooks.COMPONENT_EMIT,
     component.appContext.app,
     component,

+ 3 - 3
packages/runtime-core/src/renderer.ts

@@ -340,10 +340,10 @@ function baseCreateRenderer(
     initFeatureFlags()
   }
 
+  const target = getGlobalThis()
+  target.__VUE__ = true
   if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
-    const target = getGlobalThis()
-    target.__VUE__ = true
-    setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__)
+    setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target)
   }
 
   const {

+ 5 - 0
packages/sfc-playground/src/main.ts

@@ -2,4 +2,9 @@ import { createApp } from 'vue'
 import App from './App.vue'
 import '@vue/repl/style.css'
 
+// @ts-expect-error Custom window property
+window.VUE_DEVTOOLS_CONFIG = {
+  defaultSelectedAppId: 'id:repl'
+}
+
 createApp(App).mount('#app')

+ 2 - 1
packages/sfc-playground/vite.config.ts

@@ -9,7 +9,8 @@ const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)
 export default defineConfig({
   plugins: [vue(), copyVuePlugin()],
   define: {
-    __COMMIT__: JSON.stringify(commit)
+    __COMMIT__: JSON.stringify(commit),
+    __VUE_PROD_DEVTOOLS__: JSON.stringify(true)
   },
   optimizeDeps: {
     exclude: ['@vue/repl']