Open wbern opened 6 years ago
Hi, unfortunately I didn't test it with Vue DevTools, but I can see that indeed custom element's made with this library are not detected.
As this is non-critical issue, I will postpone fixing it. If you want to contribute - feel free to prepare PR. Regards!
They seem to be detected if you refresh the devtools using their refresh button, but the children are just a no go..
It can be due to timing - e.g. if DevTools check the page for Vue components before Web Component's Custom Elements jump in. But it have to be checked with DevTools source code - https://github.com/vuejs/vue-devtools/tree/dev/src.
I tried it using stand alone dev tool desktop app but no luck. https://www.npmjs.com/package/@vue/devtools
I made a small work-around, but it doesn't seem to work recursively.
Array.from(document.querySelectorAll('*')).filter(el => {
if(el.__vue_custom_element__) {
el.__vue__ = el.__vue_custom_element__
delete el.__vue_custom_element__;
return true;
} else if(el.__vue__) {
delete el.__vue__
return true;
}
});
Now I'm trying to make the custom elements inside my root custom element show in devtool as well.
I made a script that seemingly fixes the issue without downsides. I don't know if this will lead to consequences on the Vue-side of things, but Vue Devtools now works fine.
Include this wherever you are serving the static html file and things should work.
<script>
// problem statement, aka why this hack script exists.
// Vue Devtools, when populating a tree of vue components, will find a root vue mount point,
// then traverse down the vue component's "known" vue component children.
// but for our Custom Element implementation, Vue mount points are created at each custom element.
// This means that since Vue Devtools only searched from the first known mount point, and the first mount
// point doesn't know anything about any descendant mount points, vue devtools won't know about them.
//
// in storybook, this means that we'll only see the storybook vue component
// if we were to only mount the custom element, we would only see the custom element's vue instance,
// but not the descendant custom elements (like atoms etc.).
// this script aims to fix that. Hopefully Vue Devtools works as intended after this, otherwise we'll have to
// find more ways to patch it, I guess.
//
// by wbern
var extractVueComponentFromVueMountPoint = c => {
if (c.constructor.name === 'Vue') {
return c.$children[0];
}
return c;
};
var getVueInstance = el => el.__vue__ || el.__vue_custom_element__;
var walk = treeNode => {
var traverse = elements => {
elements.forEach(el => {
if (el.__vue_custom_element__) {
// we found a custom element,
// this _could_ be the best way to "ignore" the custom element layer.
el.__vue__ =
el.__vue_custom_element__.$children[0].$children[0];
// shouldn't be necessary
// getVueInstance(el).$options.name =
// "Custom Element: " +
// el.tagName.toLowerCase().replace(/^[A-z]/, a => a.toUpperCase());
}
if (getVueInstance(el)) {
// we found a vue component, extract it,
// create a new tree node level,
// and finally keep searching from it
var childTreeNode = {
ref: extractVueComponentFromVueMountPoint(
getVueInstance(el),
),
tagName: el.tagName.toLowerCase(),
children: [],
};
treeNode.children.push(childTreeNode);
walk(childTreeNode);
} else if (el.children && el.children.length > 0) {
// keep looking downwards for vue components
traverse(el.children);
}
});
};
if (treeNode.ref.$el.children) {
traverse(treeNode.ref.$el.children);
}
};
var repair = treeNode => {
// now we "restore" the $children reference across the components
// so that Vue Devtools can find them properly. This is hacky as hell. :-)
treeNode.children.forEach(childTreeNode => {
if (!treeNode.ref.$children.includes(childTreeNode.ref)) {
treeNode.ref.$children.push(childTreeNode.ref);
}
// keep traversing downwards
repair(childTreeNode);
});
};
// initiates everything. can be run multiple times.
var glueThePage = (rootSelector, forceVueDevtoolsRefresh = false) => {
var root = extractVueComponentFromVueMountPoint(
document.querySelector(rootSelector).__vue__,
);
var tree = {
ref: extractVueComponentFromVueMountPoint(root),
tagName: root.$el.tagName.toLowerCase(),
children: [],
};
// find all the vue components, put in the "tree" variable
walk(tree);
// put up links between the vue components, across vue mount points.
repair(tree);
let hookIsAvailable = !!window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
let vueDevtoolsIsOnComponentsTab =
hookIsAvailable &&
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.currentTab === 'components';
if (vueDevtoolsIsOnComponentsTab || forceVueDevtoolsRefresh) {
if (hookIsAvailable) {
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('flush');
} else {
throw new Error(
'Cannot force a refresh in Vue Devtools as it does not seem to be available.',
);
}
}
};
// this is the root element of where your first Vue mount point is
// for storybook, it should be 'body > #root'
var myRootElementSelector = 'body > #root';
// lets us adapt to changes, and makes sure we don't do scanning unnecessarily early.
window.addEventListener(
'DOMContentLoaded',
() => {
// check if the root element is already available
if (
document.querySelector(myRootElementSelector) &&
document.querySelector(myRootElementSelector).__vue__
) {
glueThePage(myRootElementSelector);
}
// listen for future changes
var subscriber = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation =>
mutation.addedNodes.forEach(node => {
let isRootElement =
document.querySelector(myRootElementSelector) ===
node;
let isVueInstance = !!node.__vue__;
let isCustomElement = !!node.__vue_custom_element__;
if (
isRootElement ||
isVueInstance ||
isCustomElement
) {
glueThePage(myRootElementSelector);
}
}),
);
}).observe(document.body, { childList: true, subtree: true });
},
false,
);
</script>
Edit: Updated the script to work a little better.
I made a script that seemingly fixes the issue without downsides. I don't know if this will lead to consequences on the Vue-side of things, but Vue Devtools now works fine.
Include this wherever you are serving the static html file and things should work.
<script> // .. </script>
Edit: Updated the script to work a little better.
Thank you so much for this, much appreciated!
I made a script that seemingly fixes the issue without downsides. I don't know if this will lead to consequences on the Vue-side of things, but Vue Devtools now works fine.
Include this wherever you are serving the static html file and things should work.
<script> // problem statement, aka why this hack script exists. // Vue Devtools, when populating a tree of vue components, will find a root vue mount point, // then traverse down the vue component's "known" vue component children. // but for our Custom Element implementation, Vue mount points are created at each custom element. // This means that since Vue Devtools only searched from the first known mount point, and the first mount // point doesn't know anything about any descendant mount points, vue devtools won't know about them. // // in storybook, this means that we'll only see the storybook vue component // if we were to only mount the custom element, we would only see the custom element's vue instance, // but not the descendant custom elements (like atoms etc.). // this script aims to fix that. Hopefully Vue Devtools works as intended after this, otherwise we'll have to // find more ways to patch it, I guess. // // by wbern var extractVueComponentFromVueMountPoint = c => { if (c.constructor.name === 'Vue') { return c.$children[0]; } return c; }; var getVueInstance = el => el.__vue__ || el.__vue_custom_element__; var walk = treeNode => { var traverse = elements => { elements.forEach(el => { if (el.__vue_custom_element__) { // we found a custom element, // this _could_ be the best way to "ignore" the custom element layer. el.__vue__ = el.__vue_custom_element__.$children[0].$children[0]; // shouldn't be necessary // getVueInstance(el).$options.name = // "Custom Element: " + // el.tagName.toLowerCase().replace(/^[A-z]/, a => a.toUpperCase()); } if (getVueInstance(el)) { // we found a vue component, extract it, // create a new tree node level, // and finally keep searching from it var childTreeNode = { ref: extractVueComponentFromVueMountPoint( getVueInstance(el), ), tagName: el.tagName.toLowerCase(), children: [], }; treeNode.children.push(childTreeNode); walk(childTreeNode); } else if (el.children && el.children.length > 0) { // keep looking downwards for vue components traverse(el.children); } }); }; if (treeNode.ref.$el.children) { traverse(treeNode.ref.$el.children); } }; var repair = treeNode => { // now we "restore" the $children reference across the components // so that Vue Devtools can find them properly. This is hacky as hell. :-) treeNode.children.forEach(childTreeNode => { if (!treeNode.ref.$children.includes(childTreeNode.ref)) { treeNode.ref.$children.push(childTreeNode.ref); } // keep traversing downwards repair(childTreeNode); }); }; // initiates everything. can be run multiple times. var glueThePage = (rootSelector, forceVueDevtoolsRefresh = false) => { var root = extractVueComponentFromVueMountPoint( document.querySelector(rootSelector).__vue__, ); var tree = { ref: extractVueComponentFromVueMountPoint(root), tagName: root.$el.tagName.toLowerCase(), children: [], }; // find all the vue components, put in the "tree" variable walk(tree); // put up links between the vue components, across vue mount points. repair(tree); let hookIsAvailable = !!window.__VUE_DEVTOOLS_GLOBAL_HOOK__; let vueDevtoolsIsOnComponentsTab = hookIsAvailable && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.currentTab === 'components'; if (vueDevtoolsIsOnComponentsTab || forceVueDevtoolsRefresh) { if (hookIsAvailable) { window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('flush'); } else { throw new Error( 'Cannot force a refresh in Vue Devtools as it does not seem to be available.', ); } } }; // this is the root element of where your first Vue mount point is // for storybook, it should be 'body > #root' var myRootElementSelector = 'body > #root'; // lets us adapt to changes, and makes sure we don't do scanning unnecessarily early. window.addEventListener( 'DOMContentLoaded', () => { // check if the root element is already available if ( document.querySelector(myRootElementSelector) && document.querySelector(myRootElementSelector).__vue__ ) { glueThePage(myRootElementSelector); } // listen for future changes var subscriber = new MutationObserver((mutations, observer) => { mutations.forEach(mutation => mutation.addedNodes.forEach(node => { let isRootElement = document.querySelector(myRootElementSelector) === node; let isVueInstance = !!node.__vue__; let isCustomElement = !!node.__vue_custom_element__; if ( isRootElement || isVueInstance || isCustomElement ) { glueThePage(myRootElementSelector); } }), ); }).observe(document.body, { childList: true, subtree: true }); }, false, ); </script>
Edit: Updated the script to work a little better.
Does this still work? Its giving me errors and can't seem to make it work. Using Vue 2.
Hi.
I'm wondering why I cannot inspect Vue components inside the vce on the page.
Is this a limitation with Vue Devtools, or is it something that can be fixed? Happy to hear of any work-arounds if there are any.
Thanks in advance.