Description
TL;DR
Issue:
vue-web-component-wrapper
prevents a WebComponent
#shadow-root
from containing DOM rendered slot
elements.
In its implementation, the wrap
function it provides is focused on creating CustomElements
tags that behave as VueComponents
; rather than focusing on behaving as WebComponents
defined from a VueComponent
that integrates, with shadow-DOM semantics, as a direct child-element in their #shadow-root
definition.
I've written a Vue
vue-web-component-plugin
(<=) , using a different design, that supports DOMWebComponent
slot
behavior. It is approximately ~100 lines of self-contained ready to use Javascript.msj
module code.Some of the other differences from
wrap
are:
- seamless
Vue.component
registration viaVue.component(..)
- any ancestral
$root
vue
is inherited/shared, or if none, then a$root
vue
is created as needed- lazy Vue integration of DOM upgraded custom
HTMLElements
; reducing temporal side-effects.vueDefinition
keyshadowCss
support for#shadow-root
encapsulatedcss
definitionsFor
slots
to work, I had to enhance Vue with a way to honor DOM semantics for#shadow-root
slot
elements.
Note:
The following discussion assumes some familiarity with the DOM concepts slot
, light DOM
and shadow DOM
.
For more detail on what
wrap
does, and why it is different from theplugin
I mentioned above, I will briefly present a minimal description of how VueVueComponents
work and describe a few browser terms for clarity.
The Vue system has VueComponents
(similar to classes) that are based on vueDefinitions
associated with a tagName
using Vue.component(tagName, vueDefinition)
. I will be referring to these VueComponents
as HTML macros, from here on.
When you create a new $root
vue
using new Vue(vueDefinition)
with a el:
member in the vueDefinition
(or manually invoke its' $mount
), Vue will walk the childElements and recursively perform macro substitution of all the elements whose tagName
matches the VueComponents
you registered using Vue.component(tagName, vueDefinition)
. It will continue to perform this behavior based on reactive change tracking from that point on.
It does this using the render
function defined on the vueDefinition
.
If no render
function is defined, it will look for a template
to compile into a render
function. If no template
is defined, it will extract the innerHTML
of the corresponding tagName
element, and use that as the template
.
This makes Vue a nicely designed and powerful reactive HTML macro (aka
VueComponent
) system, where arguments to thetagName
element macros are provided in their attributes, and allrendered
macro results are reactively tracked for efficient management of change propagation (observe and react patterns from Smalltalk). In that same systemtemplates
and (innerHTML
) support{{..}}
substitution rules.
The browser DOM allows you to define CustomElements
and WebComponents
.
To enable that, it provides the customElements
API to allow registering a Javascript class
as the behavior for an element with a hyphenated tagName
.
A custom element once registered is a new type of browser DOM element; depending on your browser, custom elements technology is the same technique by which it internally implements a number of the intrinsic browser elements with closed #shadow-roots
.
A custom element is not a macro and thus can't do some of the things Vue VueComponents
can, but a WebComponent
using a shadow DOM #shadow-root
can provide some capabilities that Vue VueComponents
cannot. This can be specifically seen in how it uses DOM slot
elements, and how it lazily upgrades tagName
elements when a corresponding custom element is registered, and in how it encapsulates the #shadow-root
elements including css
.
When you issue the call:
you are defining a CustomElement
. If that CustomElement
will utilize a #shadow-root
then you are defining a WebComponent
. A WebComponent
uses slot
elements within the #shadow-root
for slotting (transposing) the innerHTML
(DOM Nodes
) contained within any HTML that has that tagName
element.
What's the problem, why should I think about these things?
Given that background we now have the foundation to understand why wrap
does not provide expected WebComponent
#shadow-root
semantics and behavior, but does allow self-contained CustomElement
isolated $root
wrappering of Vue VueComponent
vueDefinitions
.
Phew, sorry you had to read all that. It is a lot of pedantically crafted explanatory discussion for saying DOM
slot
elements in aWebComponent's
shadowRoot
don't work in Vue, and that you can't useWebComponents
(defined by avueDefinition
) properly in Vue without some modifications to Vue. Specifically, we need a way to tell Vue to treat appropriately markedslot
elements, as DOMslot
elements and not as Vueslot
macros.
Basically, Vue thinks that any slot
elements it sees within a vue
subtree of VNodes
are slot
elements that it should always use as its (Vue's) own macro element directives for performing transpositional replacement of innerHTML
content into a render/template definition.
That behavior leads to a direct conflict with the DOM semantics of slot
elements in #shadow-roots
.
In other words Vue grabs all
slot
elements wherever it sees them, which withwrap
definedCustomElements
includes their#shadow-root
. Therefore the DOM never gets to see them in a wrappedWebComponents
#shadow-root
and can't perform the expected DOM slotting semantics.