1
1
// This is the backend that is injected into the page that a Vue app lives in
2
2
// when the Vue Devtools panel is activated.
3
3
4
- import { highlight , unHighlight , getInstanceRect } from './highlighter'
4
+ import { highlight , unHighlight , getInstanceOrVnodeRect } from './highlighter'
5
5
import { initVuexBackend } from './vuex'
6
6
import { initEventsBackend } from './events'
7
7
import { findRelatedComponent } from './utils'
@@ -15,13 +15,15 @@ const rootInstances = []
15
15
const propModes = [ 'default' , 'sync' , 'once' ]
16
16
17
17
export const instanceMap = window . __VUE_DEVTOOLS_INSTANCE_MAP__ = new Map ( )
18
+ export const functionalVnodeMap = window . __VUE_DEVTOOLS_FUNCTIONAL_VNODE_MAP__ = new Map ( )
18
19
const consoleBoundInstances = Array ( 5 )
19
20
let currentInspectedId
20
21
let bridge
21
22
let filter = ''
22
23
let captureCount = 0
23
24
let isLegacy = false
24
25
let rootUID = 0
26
+ let functionalIds = new Map ( )
25
27
26
28
export function initBackend ( _bridge ) {
27
29
bridge = _bridge
@@ -62,14 +64,14 @@ function connect (Vue) {
62
64
63
65
bridge . on ( 'select-instance' , id => {
64
66
currentInspectedId = id
65
- const instance = instanceMap . get ( id )
66
- bindToConsole ( instance )
67
+ const instance = findInstanceOrVnode ( id )
68
+ if ( ! / : f u n c t i o n a l : / . test ( id ) ) bindToConsole ( instance )
67
69
flush ( )
68
70
bridge . send ( 'instance-selected' )
69
71
} )
70
72
71
73
bridge . on ( 'scroll-to-instance' , id => {
72
- const instance = instanceMap . get ( id )
74
+ const instance = findInstanceOrVnode ( id )
73
75
instance && scrollIntoView ( instance )
74
76
} )
75
77
@@ -80,7 +82,7 @@ function connect (Vue) {
80
82
81
83
bridge . on ( 'refresh' , scan )
82
84
83
- bridge . on ( 'enter-instance' , id => highlight ( instanceMap . get ( id ) ) )
85
+ bridge . on ( 'enter-instance' , id => highlight ( findInstanceOrVnode ( id ) ) )
84
86
85
87
bridge . on ( 'leave-instance' , unHighlight )
86
88
@@ -145,6 +147,15 @@ function connect (Vue) {
145
147
setTimeout ( scan , 0 )
146
148
}
147
149
150
+ export function findInstanceOrVnode ( id ) {
151
+ if ( / : f u n c t i o n a l : / . test ( id ) ) {
152
+ const [ refId ] = id . split ( ':functional:' )
153
+
154
+ return functionalVnodeMap . get ( refId ) [ id ]
155
+ }
156
+ return instanceMap . get ( id )
157
+ }
158
+
148
159
/**
149
160
* Scan the page for root level Vue instances.
150
161
*/
@@ -225,6 +236,7 @@ function walk (node, fn) {
225
236
226
237
function flush ( ) {
227
238
let start
239
+ functionalIds . clear ( )
228
240
if ( process . env . NODE_ENV !== 'production' ) {
229
241
captureCount = 0
230
242
start = window . performance . now ( )
@@ -262,20 +274,27 @@ function findQualifiedChildrenFromList (instances) {
262
274
* If the instance itself is qualified, just return itself.
263
275
* This is ok because [].concat works in both cases.
264
276
*
265
- * @param {Vue } instance
277
+ * @param {Vue|Vnode } instance
266
278
* @return {Vue|Array }
267
279
*/
268
280
269
281
function findQualifiedChildren ( instance ) {
270
282
return isQualified ( instance )
271
283
? capture ( instance )
272
- : findQualifiedChildrenFromList ( instance . $children )
284
+ : findQualifiedChildrenFromList ( instance . $children ) . concat (
285
+ instance . _vnode && instance . _vnode . children
286
+ // Find functional components in recursively in non-functional vnodes.
287
+ ? flatten ( instance . _vnode . children . filter ( child => ! child . componentInstance ) . map ( captureChild ) )
288
+ // Filter qualified children.
289
+ . filter ( ( { name } ) => name . indexOf ( filter ) > - 1 )
290
+ : [ ]
291
+ )
273
292
}
274
293
275
294
/**
276
295
* Check if an instance is qualified.
277
296
*
278
- * @param {Vue } instance
297
+ * @param {Vue|Vnode } instance
279
298
* @return {Boolean }
280
299
*/
281
300
@@ -284,17 +303,64 @@ function isQualified (instance) {
284
303
return name . indexOf ( filter ) > - 1
285
304
}
286
305
306
+ function flatten ( items ) {
307
+ return items . reduce ( ( acc , item ) => {
308
+ if ( item instanceof Array ) acc . push ( ...flatten ( item ) )
309
+ else if ( item ) acc . push ( item )
310
+
311
+ return acc
312
+ } , [ ] )
313
+ }
314
+
315
+ function captureChild ( child ) {
316
+ if ( child . fnContext ) {
317
+ return capture ( child )
318
+ } else if ( child . componentInstance ) {
319
+ if ( ! child . _isBeingDestroyed ) return capture ( child . componentInstance )
320
+ } else if ( child . children ) {
321
+ return flatten ( child . children . map ( captureChild ) )
322
+ }
323
+ }
324
+
287
325
/**
288
326
* Capture the meta information of an instance. (recursive)
289
327
*
290
328
* @param {Vue } instance
291
329
* @return {Object }
292
330
*/
293
331
294
- function capture ( instance , _ , list ) {
332
+ function capture ( instance , index , list ) {
295
333
if ( process . env . NODE_ENV !== 'production' ) {
296
334
captureCount ++
297
335
}
336
+
337
+ // Functional component.
338
+ if ( instance . fnContext ) {
339
+ const contextUid = instance . fnContext . __VUE_DEVTOOLS_UID__
340
+ let id = functionalIds . get ( contextUid )
341
+ if ( id == null ) {
342
+ id = 0
343
+ } else {
344
+ id ++
345
+ }
346
+ functionalIds . set ( contextUid , id )
347
+ const functionalId = contextUid + ':functional:' + id
348
+ markFunctional ( functionalId , instance )
349
+ return {
350
+ id : functionalId ,
351
+ functional : true ,
352
+ name : getComponentName ( instance . fnOptions ) || 'Anonymous Component' ,
353
+ renderKey : getRenderKey ( instance . key ) ,
354
+ children : instance . children ? instance . children . map (
355
+ child => child . fnContext
356
+ ? capture ( child )
357
+ : child . componentInstance
358
+ ? capture ( child . componentInstance )
359
+ : undefined ) . filter ( Boolean ) : [ ] ,
360
+ inactive : false , // TODO: Check what is it for.
361
+ isFragment : false // TODO: Check what is it for.
362
+ }
363
+ }
298
364
// instance._uid is not reliable in devtools as there
299
365
// may be 2 roots with same _uid which causes unexpected
300
366
// behaviour
@@ -306,13 +372,13 @@ function capture (instance, _, list) {
306
372
renderKey : getRenderKey ( instance . $vnode ? instance . $vnode [ 'key' ] : null ) ,
307
373
inactive : ! ! instance . _inactive ,
308
374
isFragment : ! ! instance . _isFragment ,
309
- children : instance . $ children
310
- . filter ( child => ! child . _isBeingDestroyed )
311
- . map ( capture )
375
+ children : instance . _vnode . children
376
+ ? flatten ( ( instance . _vnode . children ) . map ( captureChild ) )
377
+ : instance . $children . filter ( child => ! child . _isBeingDestroyed ) . map ( capture )
312
378
}
313
379
// record screen position to ensure correct ordering
314
380
if ( ( ! list || list . length > 1 ) && ! instance . _inactive ) {
315
- const rect = getInstanceRect ( instance )
381
+ const rect = getInstanceOrVnodeRect ( instance )
316
382
ret . top = rect ? rect . top : Infinity
317
383
} else {
318
384
ret . top = Infinity
@@ -353,6 +419,18 @@ function mark (instance) {
353
419
}
354
420
}
355
421
422
+ function markFunctional ( id , vnode ) {
423
+ const refId = vnode . fnContext . __VUE_DEVTOOLS_UID__
424
+ if ( ! functionalVnodeMap . has ( refId ) ) {
425
+ functionalVnodeMap . set ( refId , { } )
426
+ vnode . fnContext . $on ( 'hook:beforeDestroy' , function ( ) {
427
+ functionalVnodeMap . delete ( refId )
428
+ } )
429
+ }
430
+
431
+ functionalVnodeMap . get ( refId ) [ id ] = vnode
432
+ }
433
+
356
434
/**
357
435
* Get the detailed information of an inspected instance.
358
436
*
@@ -362,7 +440,19 @@ function mark (instance) {
362
440
function getInstanceDetails ( id ) {
363
441
const instance = instanceMap . get ( id )
364
442
if ( ! instance ) {
365
- return { }
443
+ const vnode = findInstanceOrVnode ( id )
444
+
445
+ if ( ! vnode ) return { }
446
+
447
+ const data = {
448
+ id,
449
+ name : getComponentName ( vnode . fnOptions ) ,
450
+ file : vnode . fnOptions . __file || null ,
451
+ state : processProps ( { $options : vnode . fnOptions , ...( vnode . devtoolsMeta && vnode . devtoolsMeta . props ) } ) ,
452
+ functional : true
453
+ }
454
+
455
+ return data
366
456
} else {
367
457
const data = {
368
458
id : id ,
@@ -427,7 +517,7 @@ export function reduceStateList (list) {
427
517
*/
428
518
429
519
export function getInstanceName ( instance ) {
430
- const name = getComponentName ( instance . $options )
520
+ const name = getComponentName ( instance . $options || instance . fnOptions )
431
521
if ( name ) return name
432
522
return instance . $root === instance
433
523
? 'Root'
@@ -701,7 +791,7 @@ function processObservables (instance) {
701
791
*/
702
792
703
793
function scrollIntoView ( instance ) {
704
- const rect = getInstanceRect ( instance )
794
+ const rect = getInstanceOrVnodeRect ( instance )
705
795
if ( rect ) {
706
796
window . scrollBy ( 0 , rect . top + ( rect . height - window . innerHeight ) / 2 )
707
797
}
@@ -715,6 +805,7 @@ function scrollIntoView (instance) {
715
805
*/
716
806
717
807
function bindToConsole ( instance ) {
808
+ if ( ! instance ) return
718
809
const id = instance . __VUE_DEVTOOLS_UID__
719
810
const index = consoleBoundInstances . indexOf ( id )
720
811
if ( index > - 1 ) {
0 commit comments