From 70992d5cf40b832aed1018ac99398d67b0c4dbb2 Mon Sep 17 00:00:00 2001 From: Rahul Kadyan Date: Wed, 1 Aug 2018 13:02:08 +0200 Subject: [PATCH 1/8] feat: Display functional components in component tree --- package.json | 1 + src/backend/highlighter.js | 15 +++-- src/backend/index.js | 67 ++++++++++++++++--- .../views/components/ComponentInstance.vue | 9 +++ yarn.lock | 4 ++ 5 files changed, 81 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 9dcfe3f14..6a4c55c0c 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@vue/ui": "^0.5.1", "autoprefixer": "^9.0.2", "circular-json-es6": "^2.0.1", + "cuid": "^2.1.1", "lodash.debounce": "^4.0.8", "lodash.groupby": "^4.6.0", "uglifyjs-webpack-plugin": "^1.1.4", diff --git a/src/backend/highlighter.js b/src/backend/highlighter.js index a0834f01b..5b0c2430f 100644 --- a/src/backend/highlighter.js +++ b/src/backend/highlighter.js @@ -1,4 +1,4 @@ -import { inDoc, classify } from '../util' +import { inDoc, classify, getComponentName } from '../util' import { getInstanceName } from './index' import SharedData from 'src/shared-data' @@ -28,10 +28,10 @@ overlay.appendChild(overlayContent) export function highlight (instance) { if (!instance) return - const rect = getInstanceRect(instance) + const rect = getInstanceOrVnodeRect(instance) if (rect) { let content = '' - let name = getInstanceName(instance) + let name = instance.fnContext ? getComponentName(instance.fnOptions) : getInstanceName(instance) if (SharedData.classifyComponents) name = classify(name) if (name) content = `<${name}>` showOverlay(rect, content) @@ -55,14 +55,15 @@ export function unHighlight () { * @return {Object} */ -export function getInstanceRect (instance) { - if (!inDoc(instance.$el)) { +export function getInstanceOrVnodeRect (instance) { + const el = instance.$el || instance.elm + if (!inDoc(el)) { return } if (instance._isFragment) { return getFragmentRect(instance) - } else if (instance.$el.nodeType === 1) { - return instance.$el.getBoundingClientRect() + } else if (el.nodeType === 1) { + return el.getBoundingClientRect() } } diff --git a/src/backend/index.js b/src/backend/index.js index f190753de..d12f1a38a 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -1,13 +1,14 @@ // This is the backend that is injected into the page that a Vue app lives in // when the Vue Devtools panel is activated. -import { highlight, unHighlight, getInstanceRect } from './highlighter' +import { highlight, unHighlight, getInstanceOrVnodeRect } from './highlighter' import { initVuexBackend } from './vuex' import { initEventsBackend } from './events' import { findRelatedComponent } from './utils' import { stringify, classify, camelize, set, parse, getComponentName } from '../util' import ComponentSelector from './component-selector' import SharedData, { init as initSharedData } from 'src/shared-data' +import cuid from 'cuid' // hook should have been injected before this executes. const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__ @@ -15,6 +16,7 @@ const rootInstances = [] const propModes = ['default', 'sync', 'once'] export const instanceMap = window.__VUE_DEVTOOLS_INSTANCE_MAP__ = new Map() +export const functionalVnodeMap = window.__VUE_DEVTOOLS_FUNCTIONAL_VNODE_MAP__ = new Map() const consoleBoundInstances = Array(5) let currentInspectedId let bridge @@ -36,6 +38,7 @@ export function initBackend (_bridge) { } function connect () { + console.log('BACKEND: ', hook.Vue) initSharedData({ bridge, Vue: hook.Vue @@ -68,7 +71,7 @@ function connect () { }) bridge.on('scroll-to-instance', id => { - const instance = instanceMap.get(id) + const instance = findInstanceOrVnode(id) instance && scrollIntoView(instance) }) @@ -79,7 +82,7 @@ function connect () { bridge.on('refresh', scan) - bridge.on('enter-instance', id => highlight(instanceMap.get(id))) + bridge.on('enter-instance', id => highlight(findInstanceOrVnode(id))) bridge.on('leave-instance', unHighlight) @@ -143,6 +146,15 @@ function connect () { scan() } +export function findInstanceOrVnode (id) { + if (/:functional:/.test(id)) { + const [refId] = id.split(':functional:') + + return functionalVnodeMap.get(refId)[id] + } + return instanceMap.get(id) +} + /** * Scan the page for root level Vue instances. */ @@ -292,6 +304,26 @@ function capture (instance, _, list) { if (process.env.NODE_ENV !== 'production') { captureCount++ } + + // Functional component. + if (instance.fnContext) { + const functionalId = instance.fnContext.__VUE_DEVTOOLS_UID__ + ':functional:' + cuid() + markFunctional(functionalId, instance) + return { + id: functionalId, + functional: true, + name: getComponentName(instance.fnOptions) || 'Anonymous Component', + renderKey: getRenderKey(instance.key), + children: instance.children ? instance.children.map( + child => child.fnContext + ? capture(child) + : child.componentInstance + ? capture(child.componentInstance) + : undefined).filter(Boolean) : [], + inactive: false, // TODO: Check what is it for. + isFragment: false // TODO: Check what is it for. + } + } // instance._uid is not reliable in devtools as there // may be 2 roots with same _uid which causes unexpected // behaviour @@ -303,13 +335,19 @@ function capture (instance, _, list) { renderKey: getRenderKey(instance.$vnode ? instance.$vnode['key'] : null), inactive: !!instance._inactive, isFragment: !!instance._isFragment, - children: instance.$children - .filter(child => !child._isBeingDestroyed) - .map(capture) + children: [ + ...(instance._vnode.children ? [] : instance.$children) + .filter(child => !child._isBeingDestroyed) + .map(capture), + ...(instance._vnode.children || []) + .map((child, index) => { + if (child.fnContext) return capture(child) + else if (child.componentInstance) return capture(child.componentInstance) + }).filter(Boolean)] } // record screen position to ensure correct ordering if ((!list || list.length > 1) && !instance._inactive) { - const rect = getInstanceRect(instance) + const rect = getInstanceOrVnodeRect(instance) ret.top = rect ? rect.top : Infinity } else { ret.top = Infinity @@ -350,6 +388,18 @@ function mark (instance) { } } +function markFunctional (id, vnode) { + const refId = vnode.fnContext.__VUE_DEVTOOLS_UID__ + if (!functionalVnodeMap.has(refId)) { + functionalVnodeMap.set(refId, {}) + vnode.fnContext.$on('hook:beforeDestroy', function () { + functionalVnodeMap.delete(refId) + }) + } + + functionalVnodeMap.get(refId)[id] = vnode +} + /** * Get the detailed information of an inspected instance. * @@ -698,7 +748,7 @@ function processObservables (instance) { */ function scrollIntoView (instance) { - const rect = getInstanceRect(instance) + const rect = getInstanceOrVnodeRect(instance) if (rect) { window.scrollBy(0, rect.top + (rect.height - window.innerHeight) / 2) } @@ -712,6 +762,7 @@ function scrollIntoView (instance) { */ function bindToConsole (instance) { + if (!instance) return const id = instance.__VUE_DEVTOOLS_UID__ const index = consoleBoundInstances.indexOf(id) if (index > -1) { diff --git a/src/devtools/views/components/ComponentInstance.vue b/src/devtools/views/components/ComponentInstance.vue index bb0813336..d1212fe03 100644 --- a/src/devtools/views/components/ComponentInstance.vue +++ b/src/devtools/views/components/ComponentInstance.vue @@ -68,6 +68,12 @@ > inactive + + functional + @@ -263,6 +269,9 @@ export default { background-color #b3cbf7 &.inactive background-color #aaa + &.functional + background-color #f1f1f1 + color: #aaa &:not(.console) margin-left 6px diff --git a/yarn.lock b/yarn.lock index 2972cd198..7b5d139a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1392,6 +1392,10 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" +cuid@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.1.tgz#3b0fccbda1089ff123a127320c860b975200d62b" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" From cafaaa231c92e841e66b731b84da61c46b05a8e3 Mon Sep 17 00:00:00 2001 From: Rahul Kadyan Date: Wed, 1 Aug 2018 13:52:45 +0200 Subject: [PATCH 2/8] feat: Functional component inspector --- src/backend/index.js | 16 +++++++++++++--- .../views/components/ComponentInspector.vue | 6 ++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/backend/index.js b/src/backend/index.js index d12f1a38a..4969f976b 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -64,8 +64,8 @@ function connect () { bridge.on('select-instance', id => { currentInspectedId = id - const instance = instanceMap.get(id) - bindToConsole(instance) + const instance = findInstanceOrVnode(id) + if (!/:functional:/.test(id)) bindToConsole(instance) flush() bridge.send('instance-selected') }) @@ -409,7 +409,17 @@ function markFunctional (id, vnode) { function getInstanceDetails (id) { const instance = instanceMap.get(id) if (!instance) { - return {} + const vnode = findInstanceOrVnode(id) + + if (!vnode) return {} + + return { + id, + name: getComponentName(vnode.fnOptions), + file: vnode.fnOptions.__file || null, + state: [], + functional: true + } } else { const data = { id: id, diff --git a/src/devtools/views/components/ComponentInspector.vue b/src/devtools/views/components/ComponentInspector.vue index 2d91ff214..8c48dc635 100644 --- a/src/devtools/views/components/ComponentInspector.vue +++ b/src/devtools/views/components/ComponentInspector.vue @@ -42,6 +42,12 @@ >
Select a component instance to inspect.
+
+
Functional components have no instance and therefore no state to be inspected.
+
Date: Wed, 1 Aug 2018 14:38:43 +0200 Subject: [PATCH 3/8] feat: Process props --- src/backend/index.js | 19 ++++++++++++------- .../views/components/ComponentInspector.vue | 6 ------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/backend/index.js b/src/backend/index.js index 4969f976b..165d8b900 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -38,7 +38,6 @@ export function initBackend (_bridge) { } function connect () { - console.log('BACKEND: ', hook.Vue) initSharedData({ bridge, Vue: hook.Vue @@ -271,25 +270,29 @@ function findQualifiedChildrenFromList (instances) { * If the instance itself is qualified, just return itself. * This is ok because [].concat works in both cases. * - * @param {Vue} instance + * @param {Vue|Vnode} instance * @return {Vue|Array} */ function findQualifiedChildren (instance) { return isQualified(instance) ? capture(instance) - : findQualifiedChildrenFromList(instance.$children) + : findQualifiedChildrenFromList(instance.$children).concat( + instance._vnode && instance._vnode.children + ? instance._vnode.children.filter(child => child.fnContext).map(capture) + : [] + ) } /** * Check if an instance is qualified. * - * @param {Vue} instance + * @param {Vue|Vnode} instance * @return {Boolean} */ function isQualified (instance) { - const name = classify(getInstanceName(instance)).toLowerCase() + const name = classify(instance.fnContext ? getComponentName(instance) : getInstanceName(instance)).toLowerCase() return name.indexOf(filter) > -1 } @@ -413,13 +416,15 @@ function getInstanceDetails (id) { if (!vnode) return {} - return { + const data = { id, name: getComponentName(vnode.fnOptions), file: vnode.fnOptions.__file || null, - state: [], + state: processProps({ $options: vnode.fnOptions, ...(vnode.devtoolsMeta && vnode.devtoolsMeta.props) }), functional: true } + + return data } else { const data = { id: id, diff --git a/src/devtools/views/components/ComponentInspector.vue b/src/devtools/views/components/ComponentInspector.vue index 8c48dc635..2d91ff214 100644 --- a/src/devtools/views/components/ComponentInspector.vue +++ b/src/devtools/views/components/ComponentInspector.vue @@ -42,12 +42,6 @@ >
Select a component instance to inspect.
-
-
Functional components have no instance and therefore no state to be inspected.
-
Date: Wed, 1 Aug 2018 16:53:55 +0200 Subject: [PATCH 4/8] chore: functional components example --- shells/dev/target/Functional.vue | 5 +++++ shells/dev/target/Target.vue | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 shells/dev/target/Functional.vue diff --git a/shells/dev/target/Functional.vue b/shells/dev/target/Functional.vue new file mode 100644 index 000000000..3fbc65fac --- /dev/null +++ b/shells/dev/target/Functional.vue @@ -0,0 +1,5 @@ + diff --git a/shells/dev/target/Target.vue b/shells/dev/target/Target.vue index 9598db6ff..6240ab3e2 100644 --- a/shells/dev/target/Target.vue +++ b/shells/dev/target/Target.vue @@ -18,14 +18,25 @@ >Inspect component Mouse over
+
+ +