Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

devtools support for defineCustomElement #4356

Closed
soultice opened this issue Aug 16, 2021 · 8 comments
Closed

devtools support for defineCustomElement #4356

soultice opened this issue Aug 16, 2021 · 8 comments

Comments

@soultice
Copy link

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?

@posva
Copy link
Member

posva commented Aug 16, 2021

cc @Akryum

@NielsJorck
Copy link

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)

@DBTK1990
Copy link

DBTK1990 commented Feb 7, 2023

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
image

image

@larryval
Copy link

larryval commented Mar 1, 2023

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 ?

@DBTK1990
Copy link

DBTK1990 commented Mar 6, 2023 via email

@larryval
Copy link

larryval commented Mar 7, 2023

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
    });
...

@EranGrin
Copy link

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;
      }

yyx990803 added a commit that referenced this issue Aug 7, 2024
…omElement

Support configuring via `configureApp` option:

```js
defineCustomElement({
  // ...
}, {
  configureApp(app) {
    // ...
  }
})
```

close #4356
close #4635
@yyx990803
Copy link
Member

Closed via 6758c3c

@github-actions github-actions bot locked and limited conversation to collaborators Aug 22, 2024
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants
@yyx990803 @posva @larryval @EranGrin @NielsJorck @soultice @DBTK1990 and others