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

Web Components #220

Open
jods4 opened this issue Oct 19, 2020 · 9 comments
Open

Web Components #220

jods4 opened this issue Oct 19, 2020 · 9 comments

Comments

@jods4
Copy link

jods4 commented Oct 19, 2020

EDIT 2022: Vue eventually got some support for creating Web Components, and the shortcomings of current situation is better described in this comment of mine below.

I'm opening this issue to start a high-level discussion about potential ways of integrating native Web Component with Vue.

Context

I'm interested in using native Web Components (WC) as an internal building block inside large Vue applications.

The following are interesting but not my focus here:

  • Consuming 3rd-party WC;
  • Building re-usable WC for 3rd-party to consume.

Why Web Components?

"Vue has its own component system, why do you want WC inside your app?"

The web platform is increasingly built with WC as a primitive for further features.
Well-known examples included Scoped Styles, without pre-processing tricks, true DOM encapsulation, or extending built-in elements.

More new-ish examples include Element Internals, which is a spec that lets you:

  • associate your custom element with forms: link disable state to the enclosing form, support :disabled, :invalid CSS states, take part in form validation, submission, reset, and more.
  • have default accessibility roles and states.

These can be pretty useful and I'd only expect more in the future.

Current limitations

Nothing prevents you from creating a WC in JS code.
As long as you configure Vue compiler to recognize the tag name isn't a Vue component, everything should work just fine.

IMHO where this is lacking is when it comes to integrating your WC implementation with the rest of your app.
Most likely, you'll want to implement the contents of your custom element using Vue itself, as you're already using it for the rest of the app.

Because there's a hard boundary at your custom element (your Vue app stops right here), you'd have to create a new Vue app instance for each custom element. This is a bit wasteful.
Worse, because it's 2 different apps, there's stuff that won't work at all, most notably injection (DI).

What Vue could provide

I think it would be interesting if Vue could optionally instantiate its component as custom elements and inject itself into them so that it flows seamlessly.
Imagine you could add customElement: true to your component definition and Vue would:

  • register a native Custom Element for that component;
  • automatically pass itself in a hidden property so that the outer Vue context (esp. DI) would flow seamlessly into the custom element.

Then there's "comfort" APIs as, let's be honest, the native WC API are a pain to use. E.g. if props could optionally be reflected as attributes by Vue, if adding shadowDom: 'closed' would do what it says, etc.

I wonder to what extend this would need core Vue support vs could be implemented in a user-land library.

I'm just throwing the idea here, there are a bunch of challenges/designs to address, e.g. how would this representing the custom element surface in setup, to be used in API such as attachInternals()?

Thoughts?

@ShGKme
Copy link

ShGKme commented Oct 20, 2020

At least in Vue 2 (and it seems to me in Vue 3 too) it's possible to build a component as a Web Component. With @vue/cli you just need to build with --target wc: https://cli.vuejs.org/guide/build-targets.html#web-component.

Then you can use this WC in your app.

@jods4
Copy link
Author

jods4 commented Oct 20, 2020

@ShGKme correct me if I'm wrong, but doesn't it build a Vue entry point as a standalone WC for consumption in any web apps?
If so, this isn't the kind of scenario I'd like to discuss in this issue.

Rather, I'm interested in using native WC for components internal to an app, where those components would be efficiently merged into -- and have access to -- their parent Vue context.

@yyx990803
Copy link
Member

yyx990803 commented Oct 20, 2020

Honestly, this is the least meaningful direction I see for WCs - it really only creates a ton of extra complexity in return for... very little (as of now). At least for me, it's a trade-off I have absolutely no interest in pursuing unless it actually reduces the overall complexity in achieving the same end result.

@yyx990803
Copy link
Member

That said you can build your WC-based framework using Vue's reactivity system + template layer (or with other templating solutions, e.g. https://github.com/yyx990803/vue-lit)

@jods4
Copy link
Author

jods4 commented Oct 20, 2020

I'm gonna explore how much can be done without touching Vue core, i.e. in a library, or what the minimal core changes would be to support such a library.
Maybe compiler plugins might help pushing Vue down into those elements.

That said you can build your WC-based framework using Vue's reactivity system + template layer (or with other templating solutions, e.g. https://github.com/yyx990803/vue-lit)

Thanks for the pointer. That really ain't the same as what I have in mind, as those WC are totally disconnected from Vue. E.g. you couldn't use your directives in there nor inject stuff from DI.

@jfbrennan
Copy link

@jods4 I use a large library of Custom Elements in my Vue app every day and have had other teams doing the same. From what I gather you're mostly looking for a way to avoid this problem: "a hard boundary at your custom element (your Vue app stops right here), you'd have to create a new Vue app instance for each custom element."
Is that correct? If so, are you using Shadow DOM? If so, stop it :) Almost immediately ditched Shadow DOM and it's never been a problem.

@jods4
Copy link
Author

jods4 commented Aug 10, 2021

@jfbrennan I was thinking more of the consequences of having 2 distinct Vue apps running (host and custom element).
That means for example that you can't share DI.

I was prob. unclear because shadow dom totally is a "hard boundary" (for events, styles, etc.)

FWIW Vue 3.2 now has some custom element support, but it's geared more towards external usage, with one Vue app inside each custom element.

With that work being done, I wonder if Evan could now make an "internal mode". I'm thinking defineComponent with customElement: true instead of defineCustomElement.
The only difference is that "internal" mode is meant to be used in a Vue application and shares the same Vue instance as the main app. It's more lightweight, it reuses app configuration and has access to its DI.

It's a bit of a niche, but using custom elements to build some core components (low-level controls) can have some benefits, such as extending built-in elements (free ARIA and more).

@jfbrennan
Copy link

Vue has always worked well with Custom Elements afaik, it's one reason I use it instead of React.

I don't understand why you have more than one Vue "app". Are you instantiating another Vue app inside Custom Elements?

@jods4
Copy link
Author

jods4 commented Jun 7, 2022

2022 update: now Vue supports instancing "Vue Components" as true "Web Components" by using defineCustomElement instead of defineComponent.

I would like to make the following feedback on this feature:
it is sad that the current API hides all the new Web Component capabilities from user.

For example: I was looking to create a web component to get access to disconnectedCallback.
Currently there is no native alternative for this web api, and Vue unmounted callback doesn't cut it when there are leave animations in the DOM tree.

Another example could be creating a custom form control that integrates with the native <form> + CSS pseudo-states.

Getting access to these requires having access to the component class, which Vue hides from users.
API ideas could be to provide Vue support as a mixin:

import { vueCustomComponent } from "vue"

export class MyComponent extends vueCustomComponent(HTMLInputElement) {
  // ...
}

Or merge user-provided prototype into the class:

export default defineCustomComponent(
  /* component: */ { }, 
  /* prototype: */ { disconnectedCallback() { } })

The API also forces usage of shadow DOM, which sometimes we may not want as it creates boundaries for CSS, DOM queries and events. Would be nice if we could decide to use a component without template nor shadow DOM (just a wrapper around its actual content).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants