customFormatter.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { isReactive, isReadonly, isRef, Ref, toRaw } from '@vue/reactivity'
  2. import { EMPTY_OBJ, extend, isArray, isFunction, isObject } from '@vue/shared'
  3. import { isShallow } from '../../reactivity/src/reactive'
  4. import { ComponentInternalInstance, ComponentOptions } from './component'
  5. import { ComponentPublicInstance } from './componentPublicInstance'
  6. export function initCustomFormatter() {
  7. /* eslint-disable no-restricted-globals */
  8. if (!__DEV__ || typeof window === 'undefined') {
  9. return
  10. }
  11. const vueStyle = { style: 'color:#3ba776' }
  12. const numberStyle = { style: 'color:#0b1bc9' }
  13. const stringStyle = { style: 'color:#b62e24' }
  14. const keywordStyle = { style: 'color:#9d288c' }
  15. // custom formatter for Chrome
  16. // https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html
  17. const formatter = {
  18. header(obj: unknown) {
  19. // TODO also format ComponentPublicInstance & ctx.slots/attrs in setup
  20. if (!isObject(obj)) {
  21. return null
  22. }
  23. if (obj.__isVue) {
  24. return ['div', vueStyle, `VueInstance`]
  25. } else if (isRef(obj)) {
  26. return [
  27. 'div',
  28. {},
  29. ['span', vueStyle, genRefFlag(obj)],
  30. '<',
  31. formatValue(obj.value),
  32. `>`
  33. ]
  34. } else if (isReactive(obj)) {
  35. return [
  36. 'div',
  37. {},
  38. ['span', vueStyle, isShallow(obj) ? 'ShallowReactive' : 'Reactive'],
  39. '<',
  40. formatValue(obj),
  41. `>${isReadonly(obj) ? ` (readonly)` : ``}`
  42. ]
  43. } else if (isReadonly(obj)) {
  44. return [
  45. 'div',
  46. {},
  47. ['span', vueStyle, isShallow(obj) ? 'ShallowReadonly' : 'Readonly'],
  48. '<',
  49. formatValue(obj),
  50. '>'
  51. ]
  52. }
  53. return null
  54. },
  55. hasBody(obj: unknown) {
  56. return obj && (obj as any).__isVue
  57. },
  58. body(obj: unknown) {
  59. if (obj && (obj as any).__isVue) {
  60. return [
  61. 'div',
  62. {},
  63. ...formatInstance((obj as ComponentPublicInstance).$)
  64. ]
  65. }
  66. }
  67. }
  68. function formatInstance(instance: ComponentInternalInstance) {
  69. const blocks = []
  70. if (instance.type.props && instance.props) {
  71. blocks.push(createInstanceBlock('props', toRaw(instance.props)))
  72. }
  73. if (instance.setupState !== EMPTY_OBJ) {
  74. blocks.push(createInstanceBlock('setup', instance.setupState))
  75. }
  76. if (instance.data !== EMPTY_OBJ) {
  77. blocks.push(createInstanceBlock('data', toRaw(instance.data)))
  78. }
  79. const computed = extractKeys(instance, 'computed')
  80. if (computed) {
  81. blocks.push(createInstanceBlock('computed', computed))
  82. }
  83. const injected = extractKeys(instance, 'inject')
  84. if (injected) {
  85. blocks.push(createInstanceBlock('injected', injected))
  86. }
  87. blocks.push([
  88. 'div',
  89. {},
  90. [
  91. 'span',
  92. {
  93. style: keywordStyle.style + ';opacity:0.66'
  94. },
  95. '$ (internal): '
  96. ],
  97. ['object', { object: instance }]
  98. ])
  99. return blocks
  100. }
  101. function createInstanceBlock(type: string, target: any) {
  102. target = extend({}, target)
  103. if (!Object.keys(target).length) {
  104. return ['span', {}]
  105. }
  106. return [
  107. 'div',
  108. { style: 'line-height:1.25em;margin-bottom:0.6em' },
  109. [
  110. 'div',
  111. {
  112. style: 'color:#476582'
  113. },
  114. type
  115. ],
  116. [
  117. 'div',
  118. {
  119. style: 'padding-left:1.25em'
  120. },
  121. ...Object.keys(target).map(key => {
  122. return [
  123. 'div',
  124. {},
  125. ['span', keywordStyle, key + ': '],
  126. formatValue(target[key], false)
  127. ]
  128. })
  129. ]
  130. ]
  131. }
  132. function formatValue(v: unknown, asRaw = true) {
  133. if (typeof v === 'number') {
  134. return ['span', numberStyle, v]
  135. } else if (typeof v === 'string') {
  136. return ['span', stringStyle, JSON.stringify(v)]
  137. } else if (typeof v === 'boolean') {
  138. return ['span', keywordStyle, v]
  139. } else if (isObject(v)) {
  140. return ['object', { object: asRaw ? toRaw(v) : v }]
  141. } else {
  142. return ['span', stringStyle, String(v)]
  143. }
  144. }
  145. function extractKeys(instance: ComponentInternalInstance, type: string) {
  146. const Comp = instance.type
  147. if (isFunction(Comp)) {
  148. return
  149. }
  150. const extracted: Record<string, any> = {}
  151. for (const key in instance.ctx) {
  152. if (isKeyOfType(Comp, key, type)) {
  153. extracted[key] = instance.ctx[key]
  154. }
  155. }
  156. return extracted
  157. }
  158. function isKeyOfType(Comp: ComponentOptions, key: string, type: string) {
  159. const opts = Comp[type]
  160. if (
  161. (isArray(opts) && opts.includes(key)) ||
  162. (isObject(opts) && key in opts)
  163. ) {
  164. return true
  165. }
  166. if (Comp.extends && isKeyOfType(Comp.extends, key, type)) {
  167. return true
  168. }
  169. if (Comp.mixins && Comp.mixins.some(m => isKeyOfType(m, key, type))) {
  170. return true
  171. }
  172. }
  173. function genRefFlag(v: Ref) {
  174. if (isShallow(v)) {
  175. return `ShallowRef`
  176. }
  177. if ((v as any).effect) {
  178. return `ComputedRef`
  179. }
  180. return `Ref`
  181. }
  182. if ((window as any).devtoolsFormatters) {
  183. ;(window as any).devtoolsFormatters.push(formatter)
  184. } else {
  185. ;(window as any).devtoolsFormatters = [formatter]
  186. }
  187. }