devtools.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /* oxlint-disable no-restricted-globals */
  2. import type { App } from './apiCreateApp'
  3. import { Comment, Fragment, Static, Text } from './vnode'
  4. import type { GenericComponentInstance } from './component'
  5. interface AppRecord {
  6. id: number
  7. app: App
  8. version: string
  9. types: Record<string, string | Symbol>
  10. }
  11. enum DevtoolsHooks {
  12. APP_INIT = 'app:init',
  13. APP_UNMOUNT = 'app:unmount',
  14. COMPONENT_UPDATED = 'component:updated',
  15. COMPONENT_ADDED = 'component:added',
  16. COMPONENT_REMOVED = 'component:removed',
  17. COMPONENT_EMIT = 'component:emit',
  18. PERFORMANCE_START = 'perf:start',
  19. PERFORMANCE_END = 'perf:end',
  20. }
  21. export interface DevtoolsHook {
  22. enabled?: boolean
  23. emit: (event: string, ...payload: any[]) => void
  24. on: (event: string, handler: Function) => void
  25. once: (event: string, handler: Function) => void
  26. off: (event: string, handler: Function) => void
  27. appRecords: AppRecord[]
  28. /**
  29. * Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9
  30. * Returns whether the arg was buffered or not
  31. */
  32. cleanupBuffer?: (matchArg: unknown) => boolean
  33. }
  34. export let devtools: DevtoolsHook
  35. let buffer: { event: string; args: any[] }[] = []
  36. let devtoolsNotInstalled = false
  37. function emit(event: string, ...args: any[]) {
  38. if (devtools) {
  39. devtools.emit(event, ...args)
  40. } else if (!devtoolsNotInstalled) {
  41. buffer.push({ event, args })
  42. }
  43. }
  44. export function setDevtoolsHook(hook: DevtoolsHook, target: any): void {
  45. devtools = hook
  46. if (devtools) {
  47. devtools.enabled = true
  48. buffer.forEach(({ event, args }) => devtools.emit(event, ...args))
  49. buffer = []
  50. } else if (
  51. // handle late devtools injection - only do this if we are in an actual
  52. // browser environment to avoid the timer handle stalling test runner exit
  53. // (#4815)
  54. typeof window !== 'undefined' &&
  55. // some envs mock window but not fully
  56. window.HTMLElement &&
  57. // also exclude jsdom
  58. !window.navigator?.userAgent?.includes('jsdom')
  59. ) {
  60. const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
  61. target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [])
  62. replay.push((newHook: DevtoolsHook) => {
  63. setDevtoolsHook(newHook, target)
  64. })
  65. // clear buffer after 3s - the user probably doesn't have devtools installed
  66. // at all, and keeping the buffer will cause memory leaks (#4738)
  67. setTimeout(() => {
  68. if (!devtools) {
  69. target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null
  70. devtoolsNotInstalled = true
  71. buffer = []
  72. }
  73. }, 3000)
  74. } else {
  75. // non-browser env, assume not installed
  76. devtoolsNotInstalled = true
  77. buffer = []
  78. }
  79. }
  80. export function devtoolsInitApp(app: App, version: string): void {
  81. emit(DevtoolsHooks.APP_INIT, app, version, {
  82. Fragment,
  83. Text,
  84. Comment,
  85. Static,
  86. })
  87. }
  88. export function devtoolsUnmountApp(app: App): void {
  89. emit(DevtoolsHooks.APP_UNMOUNT, app)
  90. }
  91. export const devtoolsComponentAdded: DevtoolsComponentHook =
  92. /*@__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_ADDED)
  93. export const devtoolsComponentUpdated: DevtoolsComponentHook =
  94. /*@__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED)
  95. const _devtoolsComponentRemoved = /*@__PURE__*/ createDevtoolsComponentHook(
  96. DevtoolsHooks.COMPONENT_REMOVED,
  97. )
  98. export const devtoolsComponentRemoved = (
  99. component: GenericComponentInstance,
  100. ): void => {
  101. if (
  102. devtools &&
  103. typeof devtools.cleanupBuffer === 'function' &&
  104. // remove the component if it wasn't buffered
  105. !devtools.cleanupBuffer(component)
  106. ) {
  107. _devtoolsComponentRemoved(component)
  108. }
  109. }
  110. type DevtoolsComponentHook = (component: GenericComponentInstance) => void
  111. /*@__NO_SIDE_EFFECTS__*/
  112. function createDevtoolsComponentHook(
  113. hook: DevtoolsHooks,
  114. ): DevtoolsComponentHook {
  115. return (component: GenericComponentInstance) => {
  116. emit(
  117. hook,
  118. component.appContext.app,
  119. component.uid,
  120. component.parent ? component.parent.uid : undefined,
  121. component,
  122. )
  123. }
  124. }
  125. export const devtoolsPerfStart: DevtoolsPerformanceHook =
  126. /*@__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_START)
  127. export const devtoolsPerfEnd: DevtoolsPerformanceHook =
  128. /*@__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_END)
  129. type DevtoolsPerformanceHook = (
  130. component: GenericComponentInstance,
  131. type: string,
  132. time: number,
  133. ) => void
  134. function createDevtoolsPerformanceHook(
  135. hook: DevtoolsHooks,
  136. ): DevtoolsPerformanceHook {
  137. return (component: GenericComponentInstance, type: string, time: number) => {
  138. emit(hook, component.appContext.app, component.uid, component, type, time)
  139. }
  140. }
  141. export function devtoolsComponentEmit(
  142. component: GenericComponentInstance,
  143. event: string,
  144. params: any[],
  145. ): void {
  146. emit(
  147. DevtoolsHooks.COMPONENT_EMIT,
  148. component.appContext.app,
  149. component,
  150. event,
  151. params,
  152. )
  153. }