customFormatter.ts 5.1 KB

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