devtools support for defineCustomElement
What problem does this feature solve?
Thank you for the great work on the defineCustomElements
. I'm really enjoying this feature, and I'm sure many others so so as well.
Unfortunately the devtools are currently unable to find any components created with defineCustomElements
.
From a quick glance it seems they are initialized with the .mount()
method of an app, which defineCustomElements
does not make use of.
It would be a really nice addition if we were able to debug our components with the vuejs devtools.
What does the proposed API look like?
I'm not that in-depth with vue behind the scenes. Maybe it would be possible to hook into connectedCallback
and disconnectedCallback
to initialize devtools?
I'm far from in-depth with the core functionality of vue. But I did some digging and posting it here in case it is helpful. 🙈
The dev tools isn't showing since no apps are registered with the dev tools. If you mount a regular vue app on the same page it will show normally, but only have the regular app registered.
Can see the apps registered on window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps
Regular vue registers the apps on the mount()
function on createApp
in runtime-core
project apiCreateApp.ts
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}
It is then removed from devtools on unmount
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = null
devtoolsUnmountApp(app)
}
As @soultice mentions it looks natural to put it as part of connectedCallback()
and disconnectedCallback()
of the runtime-dom
apiCustomElement
.
The devtools methods aren't exported from runtime-core
, so can't call them unless changed.
The app
param is of type App, which is the return of createApp
. Which is as I see it rather far from the custom element.
The version
param is as far as I see, used to determine which backend to use in dev tools. (Vue 1-3).
As I see it either the customElement needs to be wraped or transformed to an app type to be registered as a regular vue app is. Or a different backend is required for dev tools to handle customElements. (Registering the app will still require some work)
my solution to whom ever who will need this @NielsJorck your comment was a big help, thank you
import { VueService } from "../../registries/services/vue.service";
import { ComponentInternalInstance, defineCustomElement as vueDefineCustomElement, render, version, VNode, devtools } from "vue";
const Fragment = Symbol('Fragment');
const Text = Symbol('Text');
const Comment = Symbol('Comment');
const Static = Symbol('Static');
export function wrapperVueCustomElement(customElement: ReturnType<typeof vueDefineCustomElement>, vueService: VueService) {
class VueCustomElement2 extends customElement {
private rootVNode: VNode | null = null;
constructor(initialProps: any) {
super(initialProps);
console.debug("constructed VueCustomElement2:tagName:", this.tagName);
//create div inside shadow root append id root to it
const div = document.createElement('div');
this.shadowRoot!.appendChild(div);
div.id = "root";
//@ts-ignore
this._update = function () {
//@ts-ignore
this.rootVNode = this._createVNode();
render(this.rootVNode, div);
}
}
connectedCallback() {
console.debug("connectedCallback", this);
vueService.CurrentRoot = this.shadowRoot;
// vueService.CurrentApp._container = this.shadowRoot;
super.connectedCallback();
const rootContainer = this.shadowRoot?.firstElementChild as HTMLElement;
vueService.CurrentApp._container = rootContainer;
//@ts-ignore
rootContainer.__vue_app__ = vueService.CurrentApp;
{
vueService.CurrentApp._instance = this.rootVNode?.component as ComponentInternalInstance;
devtools.emit('app:init', vueService.CurrentApp, version, {
Fragment,
Text,
Comment,
Static
});
}
vueService.StyleMountService.mountStyles(vueService.CurrentWebComponent.host, vueService.TeleportRef);
}
disconnectedCallback() {
console.debug("disconnectedCallback");
vueService.CurrentRoot = null;
vueService.destroy();
// @ts-ignore
render(null, this.CurrentApp._container);
{
vueService.CurrentApp._instance = null;
devtools.emit('app:unmount', vueService.CurrentApp);
}
// @ts-ignore
delete vueService.CurrentApp?._container?.__vue_app__;
vueService.CurrentApp._container = null;
super.disconnectedCallback();
}
}
return VueCustomElement2;
}
The line devtools.emit('app:init', vueService.CurrentApp, version, {Fragment, Text, Comment, Static}); is emitting an event to mount the Vue devtools to the browser. The event is named "app:init" and it takes in arguments the current app instance (vueService.CurrentApp), the version of Vue being used, and the symbols for Fragment, Text, Comment, and Static.
The line devtools.emit('app:unmount', vueService.CurrentApp); is emitting an event to unmount the Vue devtools from the browser. The event is named "app:unmount" and it takes in argument the current app instance (vueService.CurrentApp).
you can see the results in the images
hi DBTK1990, Could you explain just a bit how to use your script ! my entire app is mounted in an shadowdom target and my expectation is that I can use the VueDevTool to inpect Vue component, is your script aim for that ?
yes it is but you dont need all of it to do it, you can just emit the event with the current app do you have discord?
you can use this event with the vue app
devtools.emit('app:init', vueService.CurrentApp, version, { Fragment, Text, Comment, Static });
On Wed, 1 Mar 2023 at 12:37, larry @.***> wrote:
hi DBTK1990, Could you explain just a bit how to use your script ! my entire app is mounted in an shadowdom target and my expectation is that I can use the VueDevTool to inpect Vue component, is your script aim for that ?
— Reply to this email directly, view it on GitHub https://github.com/vuejs/core/issues/4356#issuecomment-1449835833, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEXKOFCPHCSKQTD6TLYX5V3WZ4RH7ANCNFSM5CIBEKLQ . You are receiving this because you commented.Message ID: @.***>
Hi DBTK1990, my Discord userName : _larry_
As I said, my entire app is attached to a shadowRoot tag, as shown in the simplified code below, I just added your code by replacing vueService.CurrentApp
by the instance of my application app
.
VueDevtools works in this config (Pinia ok, cool!), but there is something that doesn't work which is very practical in a teamwork, it's to be able to inspect the tree of Vue components (mouse hover) and highlight them in the browser. This makes it possible to understand the nesting of the components. Unfortunately, it still does not work ;(
//--- all the bootstrap code here ....
const app = createApp(App);
//---- Shadow
let targetShadow = document.getElementById("app");
const shadowRoot = targetShadow!.attachShadow({ mode: 'open' });
//--- Append Vue app
const appNode = document.createElement("div");
shadowRoot.append(appNode);
//--- Mount Vue.js App
app.mount(appNode);
//--- Your code
devtools.emit('app:init', app, version, {
Fragment,
Text,
Comment,
Static
});
...
Hello, I created a plugin to support web-component for vue3 https://www.npmjs.com/package/vue-web-component-wrapper in the latest version, I had to solve the same issue.
I solved it like this
// Add support for Vue Devtools
if (process.env.NODE_ENV === 'development' && window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
const root = document.querySelector(elementName);
app._container = root;
app._instance = inst;
const types = {
Comment: Symbol('v-cmt'),
Fragment: Symbol('v-fgt'),
Static: Symbol('v-stc'),
Text: Symbol('v-txt'),
};
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('app:init', app, app.version, types);
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = app;
}