# Web Components Across the Major Frameworks This is a pretty simple and quick overview of creating web components with Vanilla JS, HTML, &amp; DOM APIs. Then we move into comparisons between creating components in vanilla JS and other frameworks. There are some interesting differences and gotchas in each where some of them are not completely compatible with everything around web components. For more in-depth knowledge, see [Awesome Web Components](https://github.com/web-padawan/awesome-web-components). ## Vanilla JS Vanilla JS makes things pretty simple. There's no compile step and the most you may need is a polyfill for scoped custom elements. All one needs is a browser that supports the standards and you're in business. This means that any of the following examples can actually be cut and pasted into any browser window's console in order to demonstrate how they work. They'll all show up at the bottom of the page. You might have to scroll more after adding the elements to the page. ### Web Components [Overview of Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components#custom_elements_2) &mdash; Has an overview of the basics and the lifecycle methods. - [Custom Element Best Practices](https://web.dev/custom-elements-best-practices/) ```js // This is a self-contained component that does nothing but display a bolded "HI!" class CustomParagraph extends HTMLElement { constructor() { super() // set the content this.innerHTML = `<p><b>HI!</b></p>` } } // register the web component customElements.define('custom-paragraph', CustomParagraph) // create a div to contain the Web Component const d1 = document.createElement('div') // attach the div to the end of the page document.querySelector('body').appendChild(d1) // use the web component inside the div d1.innerHTML = `<custom-paragraph></custom-paragraph>` ``` ### Shadow DOM If a component doesn't call `this.attachShadow`, then it has no encapsulation and everything in it will be affected by styles from outside of the component. Conversely, your styles used without a shadow root will also spread across the whole scope of the page, potentially introducing bugs. There are also other element scoping aspects that help provide partial isolation of what's inside your component. It's not fool-proof, but it's better than not using it. - [MDN: Using shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) - [Open vs. Closed Shadow DOM](https://blog.revillweb.com/open-vs-closed-shadow-dom-9f3d7427d1af) - [Shadow DOM v1 - Self-Contained Web Components](https://web.dev/shadowdom-v1/) Here is the component from above rewritten to use the shadow DOM ```js // This is a self-contained component that does nothing but display a bolded "HI!" class CustomParagraphShadow extends HTMLElement { constructor() { super() // start using the shadow DOM's shadowRoot this.attachShadow({mode: 'open'}) // set the content this.shadowRoot.innerHTML = `<p><b>HI!</b></p>` } } // register the web component customElements.define('custom-paragraph-shadow', CustomParagraphShadow) // create a div to contain the Web Component const d2 = document.createElement('div') // attach the div to the end of the page document.querySelector('body').appendChild(d2) // use the web component inside the div d2.innerHTML = `<custom-paragraph-shadow></custom-paragraph-shadow>` ``` ### Properties Properties are literally just that &mdash; they are properties of the class that are able to be set once the class is instantiated. For example: ```ts class ColorizedParagraphProp extends HTMLElement { el; constructor() { super(); // start using the shadow DOM's shadowRoot this.attachShadow({mode: 'open'}) // create a paragraph element this.el = document.createElement('p') // set the content this.el.innerHTML = `<slot></slot>` this.shadowRoot.appendChild(this.el) } // interface for accepting new values for a property set colorProp(newValue) this.el.style.color = newValue } } // register the web component customElements.define('colorized-paragraph-prop', ColorizedParagraphProp) // create a div to contain the Web Component const d3 = document.createElement('div') // attach the div to the end of the page document.querySelector('body').appendChild(d3) // Now when you want to change the property, run this: d3.querySelector('colorized-paragraph-prop').colorProp = '#ff0' ``` ### Attributes Attributes are values that can be passed into a component through DOM attributes. In this particular example, using a shadow DOM prevents the style from higher-up interfering with the styling in the component. ```js class ColorizedParagraphAttr extends HTMLElement { el; constructor() { super(); // start using the shadow DOM's shadowRoot this.attachShadow({mode: 'open'}) // create a paragraph element this.el = document.createElement('p') // set the content this.el.innerHTML = `<slot></slot>` this.shadowRoot.appendChild(this.el) } // interface for exposing which properties this component recognizes static get observedAttributes() { return ['colorAttr']; } // interface for accepting new values for a property attributeChangedCallback(name, _oldValue, newValue) { switch (name) { case 'colorAttr': this.el.style.color = newValue break } } } // register the web component customElements.define('colorized-paragraph-attr', ColorizedParagraphAttr) // create a div to contain the Web Component const d3 = document.createElement('div') // attach the div to the end of the page document.querySelector('body').appendChild(d3) // use the web component inside the div d3.innerHTML = `<colorized-paragraph-attr colorAttr="#00e"><img src="//s.gravatar.com/avatar/44645c684eb67fc38f700fdf5ff4ff2c?s=32">&nbsp;Hello!</colorized-paragraph>` // Now when you want to change the property, run this: d3.querySelector('colorized-paragraph-attr').setAttribute('colorAttr', '#ff0') ``` ### Slots Slots are wonderful shortcuts for placing bits of nested HTML (or other content) inside another component. Without slots, everything you'd want to pass into a component to display would need to be a property. As we saw above, having a property on a web component requires additional overhead in terms of code. Slots are all managed by the DOM engine so all the code that handles it is compiled in the browser. - provided by the [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) element and allow placing content inside the WebComponent in specific places determined by the slots in the WebComponent. - [The slot property](https://developer.mozilla.org/en-US/docs/Web/API/Element/slot) &mdash; used to specify which slot an element will be put inside. - [The MDN docs on slots along with their example](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots#a_more_involved_example) - [HTMLSlotElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement) &mdash; Specifics about the slot interface if you're curious. This interface allows querying what has been put in each slot along with setting up an event listener to know when the content has been changed out. This is actually a very interesting interface that has some really cool possibilities. Here's a simple example of a component using a slot to allow devs using the component to place an additional DOM fragment inside of the slot. Just imagine how much extra boilerplate would be required to type out the following example if it used a prop to manage rendering what is put in the slot. One would need to add the property, make sure it gets updated properly, then render it each change. Whereas this just works with no extra effort: ```js // Simple custom button that uses a default slot to accept the inner content class CustomButton extends HTMLElement { constructor() { super() // start using the shadow DOM's shadowRoot this.attachShadow({mode: 'open'}) } connectedCallback() { this.render() } render() { // (note this removes any event listeners attached to objects under the shadow root) this.shadowRoot.innerHTML = `<button><slot>LABEL UNDEFINED!</slot></button>` } } // register the web component customElements.define('custom-button', CustomButton) // create a paragraph to contain the Web Component const p1 = document.createElement('p') // attach the paragraph to the end of the page document.querySelector('body').appendChild(p1) p1.innerHTML = `<custom-button>This is a button</custom-button>` ``` Here's a more advanced example that doesn't just use the default slot. It uses two named slots to allow the dev writing the HTML to place HTML inside each slot based on the `slot` attribute given to each element. ```js // Component with a label, input, and button that uses named slots to accept the inner content class CheesePref extends HTMLElement { constructor() { super() // create the shadow root this.attachShadow({mode: 'open'}) } connectedCallback() { this.render() } render() { // If there are no slots filled, it will display exactly as below // (note this removes any event listeners attached to objects under the shadow root) this.shadowRoot.innerHTML = ` <div class="preference"> <label for="cheese"><slot name="label">CHEESE LABEL!</slot></label> <input type="text" name="cheese" id="cheese" /> <button><slot name="submit">SUBMIT LABEL!</slot></ </div>` } } // register the web component customElements.define('cheese-pref', CheesePref) // create a paragraph to contain the Web Component const p2 = document.createElement('p') // attach the paragraph to the end of the page document.querySelector('body').appendChild(p2) // use the component, passing in the two named slots p2.innerHTML = ` <cheese-pref> <span slot="label">Do you like cheese?</span> <span slot="submit">Send us your preference</span> </cheese-pref>` ``` ### Events - Plain old DOM events and CustomEvents. - Listen via [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) - [Creating and triggering events](https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events) &mdash; has an overview of `CustomEvents` which is necessary to understand what to do in Lit, since it's just a simple wrapper. - [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) - [dispatchEvent](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent) For example, to listen to our `<custom-button>` above we can just attach a click listener to the component directly. Any clicks on any of the child elements will 'bubble up' through the DOM and our event listener will be able to handle it. There's no need to attach an event listener to the button only to re-emit the same event. ```js // NOTE: We are just listening to the even of the top level element // and we never actually need to listen to for the inner button event unless // we had to do something extra before re-emitting the event. document.querySelector('custom-button').addEventListener('click', function(e) {console.log(`Clicked: ${e}`)}) ``` However, if we caught the event to do something useful inside the component, then we could emit a custom event if we want the outside world to know about it: ```js class CounterButton extends HTMLElement { count; constructor() { super() // initialize count this.count = 0; } connectedCallback() { this.innerHTML = `<button></button>` // attach our event listener to increment the count this.querySelector('button').addEventListener('click', () => this.incrementCount()) this.render() } // increment the count, render, and dispatch the event incrementCount() { const count = ++this.count this.render() this.dispatchEvent(new CustomEvent('increase', {detail:{count}})) } render() { // this preserves our event listener by not replacing any HTML this.querySelector('button').innerHTML = this.count } } // register the web component customElements.define('counter-button', CounterButton) // create a paragraph to contain the Web Component const p3 = document.createElement('p') // attach the div to the end of the page document.querySelector('body').appendChild(p3) // use the component p3.innerHTML = `<counter-button></counter-button>` // listen to our custom event `increase` and log the new value p3.querySelector('counter-button').addEventListener('increase', (e) => console.log(e.detail.count)) ``` ### Context Protocol The [Context Protocol proposal](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md) has all the details on how the contexts in web components and Lit work. The protocol itself is really simple and works for all web components. Context requesters emit a custom `context-request` DOM event that contains a value indicating what context the data is being requested from, a callback function, and a Boolean indicating if the callback should be subscribed. This bubbles up the DOM. When a context provider receives it, it should `stopPropagation()` and handle the request. :::danger **Security** While this has been made to make things quite a bit easier to wire data through the UI between many layers of components, it really shouldn't be used used for anything you don't want anything on the page getting a hold of. Because of the way events work and how the DOM isn't totally sealled off from being modified, any script running on a particular page can effortlessly inject itself as either a consumer or provider of a context. This means that people can easily pretend to be a provider of a context by injecting a context provider element as a child element of the original context provider element and then intercept all the requests from the child components. Just as easily, one could insert a context requester as a child element of the provider and then request all the data or objects one wants from the provider. This also means that creating a component the acts as a man-in-the-middle attack is simple. This is why only data that should be visible on a page should be included in a context. If anything like a client that provides a connection to a websocket was passed from a context provider to a consumer, then anything that could inject a component on the page could do whatever they want with that socket. This is also why an sensitive information should not be passed back and forth over an object obtained from context. It might have been compromised and one doesn't have any way of knowing. Instead of using contexts or property passing, the code should be structured so that all of the private aspects are inside of a closure where no other code can obtain access (due to it being in a different execution context). Then the public facing portion of the code may be exposed in a way that doesn't expose the shared private data in the closure. This provides the most security. But it is a borwser environment and as you should be able to see from the following section, code running in the browser has a great ability to change the execution environment and features of the browser. ::: ### Scoped Custom Elements Scoped custom elements are a way of ensuring each shadow root has it's own custom element registry instead of relying on the one global custom element registry. Currently it is not implemented in the browser APIs, so one needs a polyfill that does a lot of monkey patching of existing browser objects. None of the following will work without the polyfill loaded. - [Scoped Custom Element Registries](https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Scoped-Custom-Element-Registries.md) - [Scoped CustomElementRegistry polyfill prototype](https://github.com/webcomponents/polyfills/blob/master/packages/scoped-custom-element-registry/README.md) ```js // Copy and paste // https://raw.githubusercontent.com/webcomponents/polyfills/master/packages/scoped-custom-element-registry/src/scoped-custom-element-registry.js // into the dev console, then copy and paste the below into the dev console to see the result. // This is a self-contained component that does nothing but display a bolded "HI!" // Note, we never register this globally, because it gets registered in the shadow // DOM's monkey patched custom element registry class CustomParagraph2 extends HTMLElement { constructor() { super() this.innerHTML = `<p><b>HI!</b></p>` } } // This is a component that takes another component class as a property // It then renders that class inside. class RenderBlock extends HTMLElement { registry; component; constructor() { super(); // The polyfill must be loaded to in order to create the CustomElementRegistry // it replaces all of the behaviour of the registry, // but also allows instantiating multiple copies const customElements = (this.registry = new CustomElementRegistry()) // Passing in the new CustomElementRegistry into the shadowDOM: // Normally attachShadow does not accept the customElements parameter, but the polyfill // replaces the function with its own. // After the polyfill has been loaded the shadowDOM API has been monkey patched // to expose a few new interfaces. this.attachShadow({mode: 'open', customElements}) } // Let the DOM know we take a `component` attribute static get observedAttributes() { return ['component']; } // handle changing out the component defined in the registry attributeChangedCallback(name, _oldValue, newValue) { switch (name) { case 'component': // since there are no other components in our registry, just use `child-elem` this.registry.define('child-elem', (this.component = newValue)) break } this.render() } connectedCallback() { this.render() } render() { const shadow = this.shadowRoot // This does the same thing as document.createElement, but in the // shadowDOM using the new CustomElementRegistry created above. // The this.shadowRoot.createElement does not exist until the polyfill // is loaded as this is one of the monkey patched APIs. const el = shadow.createElement('child-elem') // replace all the children (note this removes any event listeners attached to objects under the shadow root) shadow.replaceChildren(el) } } // register the web component customElements.define('render-block', RenderBlock) // create an instance of the component const rb = document.createElement('render-block') // set the `component` attribute rb.setAttribute('component', CustomParagraph2) // add the RenderBlock to the body document.querySelector('body').appendChild(rb) ``` ## Lit Writing web components with Lit is basically just writting with components with a thin veneer of helpers over the web components. There are a few differences: - there are helpers for dealing with rerendering components - there are helpers for dealing with the CSS in the shadow DOM ### Web Components Lit literally extends the base web component class and is completely compatible with everything in the WebComponent standard OOTB. ### Shadow DOM [Working with Shadow DOM](https://lit.dev/docs/components/shadow-dom/) ### Properties/Attributes Lit has decorators that simplify creating props. These allow turning class variables into properties without having to implement the underlying setters that would request the render. It also creates the machinery for `observedAttribues` and `attributeChangedCallback`. Otherwise, the underlying mechanism is exactly the same as in vanilla JS web components. Lit also provides a helper in the html template generator that allows setting properties through the template string: ``` html`<custom-button .colorProp=${colorProp}>Say cheese!</cutom-button> ``` This would be equivalent to the vanilla JS: ``` const customB = document.createElement('custom-button') customB.colorProp = colorProp customB.intterHTML = `Say cheese!` ``` ### Slots Lit uses the underlying `<slot>` element with no modifications. ### Events Lit uses the underlying Vanilla JS + DOM methods very directly - [Lit Events](https://lit.dev/docs/components/events/#content) - `@click=${this.something}` property in the Lit html template is a shortcut for running `addEventListener('click', this.something)` after selecting the rendered HTML. - [Lit Events: Dispatching events](https://lit.dev/docs/components/events/#dispatching-events) - This is the same as vanilla JS. ### Context Protocol This is provided by [@lit-labs/context](https://github.com/lit/lit/tree/main/packages/labs/context) which is really just an implementation in Lit of the Context Protocol from above. There's nothing special about it and anything that implements itself as a WebComponent can use a context provider or become a context provider. See the above security warning in the vanilla JS section. ### Scoped Custom Elements This is the same mechanism as in Vanilla JS. The only difference in that there is a mix-in that is used to automatically add the feature to `LitElement`. - [@lit-labs/scoped-registry-mixin](https://github.com/lit/lit/blob/main/packages/labs/scoped-registry-mixin/README.md). One still needs to bundle the polyfill used above in order for this to work properly. ## Svelte Svelte is a framework that has a specific set of language supersets and a "compiler" that parses and tranforms the code into vanilla JS. The produced JS uses the Svelte runtime library which wraps a lot of the DOM API. For some examples of how Svelte is transpiled, see the output JS in the [Svelte Dev Examples](https://svelte.dev/examples/component-events). ### Web Components Svelte has a concept of a "component", but it very different than the web component approach. It takes `*.svelte` files and compiles them into classes that can be instantiated and attached to a DOM element. These are self contained classes that know how to render themselves and only re-render when a property changes. However when one writes a component that uses another Svelte component, the custom element registry is not used. Instead, when it is compiled, all the HTML is transformed into calls to JS functions that construct the same structure and references to the component are replaced by a reference to a class derived from [`SvelteComponent`](https://github.com/sveltejs/svelte/blob/6f508a011bc8051ab9f82cafa97c5292a4453b92/packages/svelte/src/runtime/internal/Component.js#L451). However, Svelte components can be easily transformed into custom elements (web components) using the [custom elements API](https://svelte.dev/docs/custom-elements-api): > Custom elements are created from Svelte components using a wrapper approach. This means the inner Svelte component has no knowledge that it is a custom element. The custom element wrapper takes care of handling its lifecycle appropriately. Internally, when a Svelte component is turned into a custom element, it inherits from [`SvelteElement`](https://github.com/sveltejs/svelte/blob/6f508a011bc8051ab9f82cafa97c5292a4453b92/packages/svelte/src/runtime/internal/Component.js#L399) which does a lot of the heavy lifting for handling properties and rerendering. On the other hand, using web components with Svelte is super easy. Just import any needed polyfills and write HTML that uses the globally registered version of the component. Attach event listeners and props as usual for Svelte. For an example, [here's a demo that uses a Shoelace button inside Svelte](https://github.com/abnazhor/svelte-shoelace-config/blob/master/src/routes/index.svelte). ### Shadow DOM When not using Svelte's custom elements API, Svelte does not use the shadow DOM. When the [custom elements API](https://svelte.dev/docs/custom-elements-api) is used, there is a [`shadow` option](https://svelte.dev/docs/custom-elements-api#component-options) that can be set to "none" to prevent shadowRoot creation, but otherwise all generated web components will use the shadow DOM by default. ### Properties/Attributes Properties in Svelte are basically just properties passed into the constructor. If a property value changes, then the state management invalidates the value on the context, marking it dirty and setting the new value. Then a new HTML fragment is created to replace the old one, this uses the new value from the context object. Over at https://svelte.dev/examples/declaring-props, replace the App.svelte with the following code: ```html <script> import Nested from './Nested.svelte'; let count = 1; // the `$:` means 're-run whenever these values change' $: doubled = count * 2; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Count: {count} </button> <Nested answer={doubled} /> ``` Looking at the compiled code, one can see how handles creating fragments with values indexed to a `Array` context. Every property corresponds to a specific index in this context `Array`. As properties are invalidated, new HTML fragments are generated with different values. Configuring a Svelte component to be a custom element wires up all the Svelte props to the DOM attributes automatically. ### Slots By default, Svelte sidesteps using the DOM for slots and implements slots on top of the context object used for properties. All the compiled code removes the reference to the `<slot>` element completely. This allows Svelte to do more lazy rendering than the DOM would normally do for slots. For an example see the compiled code for the [Slots example](https://svelte.dev/examples/slots). Svelte custom elements however, appear use HTML slots [need to verify with a locally compiled example to see if the compiler changes it's tactics or if it uses the same framework of slots in software]. It also has some helpers for accessing the elements that have filled the slots. See [Special elements](https://svelte.dev/docs/special-elements). ### Events Svelte uses plain DOM events. It does have a helper similar to Lit's `@`_eventname_ syntax, see [on:eventname](https://svelte.dev/docs/component-directives#on-eventname) ### Context Protocol There is no equivalent of the context protocol. However, any web component written using Svelte internally could still implement it's own event emitter that conforms to the protocol in order to use context providers. Depending on the integration required, one might use the [`extend`](https://svelte.dev/docs/custom-elements-api#component-options) parameter to extend the web component to allow it to invalidate params based on the callback from the provider. This would rely on knowledge of the internals of each version of Svelte. ### Scoped Custom Elements Out of the box, Svelte doesn't support scoped custom elements. One would have to use vanilla JS or Lit in order to create an element with a shadow DOM that has it's own custom element registry. Then the Svelte code could use the elements registered with that shadow DOM context as though it was using globally registered customed element. If one really wants to be a Svelte purist, one might use the [`extend`](https://svelte.dev/docs/custom-elements-api#component-options) parameter to extend the web component to add in a `CustomElementRegistry` and pass it to `attachShadow`. One would need to take care to pass false as the value of the `use_shadow_dom` parameter of the `super()` method ([see the source that needs to be overriden](https://github.com/sveltejs/svelte/blob/6f508a011bc8051ab9f82cafa97c5292a4453b92/packages/svelte/src/runtime/internal/Component.js#L192)). ## Vue Vue actually takes a pretty similar approach to Svelte. It takes code written in single file components, `*.vue` files, and then runs them through a "compilation" step, which pretty drastically transforms the code. It has a fairly extensive runtime library which provides DOM manipulation along with some state helpers. For an example of "compiled" code, see [one of the examples of a single file component](https://play.vuejs.org/#eNp9kUFLwzAUx79KfJcqzA3ZbXQDlYF6UFHBSy6je+sy0yQkL7NQ+t19SVn1ILv1/X//l/7SdnDr3PQYERZQhsorRyIgRbeSRjXOehKd8LgTvdh524iCq4U00lTWBBJNqMUy8cviAbW24tN6vb0orqQpZ8NxfBAPhI3TG0KehCj3N6uuy8t9X854yqkyLpI4Xjd2i3opgbkERuVs3IYJUOBX71Q9PQRr2LpLuxIq2zil0b84UqwmYSEySWzDZt9POSMfcXLKqz1WX//kh9CmTMKrx4D+iBJGRhtfIw14/f6MLT+PkM2j5vYZ+IbB6pgch9pdNFvW/tPLto/52ytTf4R1S2jC6VJJNDX73JfA/+P+zNV/defTed6Tpof+B7x8phs=). Also, [while viewing the examples](https://vuejs.org/examples/), one can inspect the right most iframe to see the compiled modules in `<script>` tags in the head. Examining the code, one can see that view has a farily nice runtime library for contructing HTML elements and attaching listeners. ### Web Components Vue has it's own concept of - [Components Basics](https://vuejs.org/guide/essentials/component-basics.html) - [Vue and Web Components](https://vuejs.org/guide/extras/web-components.html) ### Shadow DOM Out of the box, Vue does not use the Shadow DOM. However, one can use Vue's `defineCustomElement` wrapper and the whole app becomes wrapped in a web component, presumably with a shadow DOM. ### Properties/Attributes Vue has it's own system for defining and validating props. It is however, independent of the custom elements/web components props model. See [more details here](https://vuejs.org/guide/components/props.html). ### Slots Vue supports [Slots](https://vuejs.org/guide/components/slots.html). Like Svelte, it has a mechanism for handling slots outside of the DOM. This can be seen by [looking at the generated JS for one of the Slot examples](https://play.vuejs.org/#eNp9Uttu1DAQ/ZXBFSpImyzd7XIJoRKtigQPgIBHv2SdSXDXsS1fSqpq/52x0y4pqvrmOXNm5pzx3LKP1pbXEVnFai+ctAE8hmjPuJaDNS7Ap0aLm/MYgtHQOTPAcbmcYan4mOt6OVVTHQUBB6uagBQB1DN2BgAulBQ7GBDqZ0UBXpkAwuiAOkBRJA4P9XwIQfXy0JQt2H8CkvqHM7eTYKEa7z9w1iV+sQ2as5yn/mnq8mymwMSg8E5Amj+1eDg6u/PhRqVneegKt6mpMMq4Co66rnuf4m0jdr0zUbcVKKmxcUXvmlaSzRfrk02L/QKOTlft+t0KVpvnFLw+fYNd93KqNq5FaqeNxgzYpm2l7ivY2BFOXtkxo0PjeqkzOCsr0pzoK3h7B4vofBJnjaQ9O8L2+dOyFVpo8PQBnezLK280rTMb4kyYwUqF7psN0mjPWTVZTblGKfPnS8aCi7i4x8VvFLtH8Cs/Joyz7w49umvk7JAL5ALDlL78+RVHeh+Sg2mjIvYTyR/ojYpJ40Q7p6WT7Bkvq/2cT5qW+MtfjnRu/t5UEpqY+8znjG7q4gnr/+Suy3Wuo32y/V/8ixns). ### Events Vue uses normal DOM events under the hood. It also has extensive helpers for reacting to and emitting events. See [Component Events](https://vuejs.org/guide/components/events.html). Additionally, it's interesting to see how the reactivity and computed values are provided through events. See the ["onUpdate:modelValue" event handler in this example](https://play.vuejs.org/#eNp9U8mS2jAQ/ZWOLsYVkCs1NwKkyBSHySEzSeaoi8q0QYMtOVowFOV/T8vCTLaai5d+T69fL7qwddvyY0A2ZwtXWtV6cOhDuxJaNa2xHi5gsZpCJ325hx4qaxrI6EQmtNCl0c7Dz4DOK6NhGbmTLMtHRGrXoR3j3648B8EFWddnIJaXSoN81WikPXD4OMujitBD3smITkG6sy5horEb1XJYruAiNICq/gC40ls8PVLiT1kOK5h9yBMPrr74UdYByV32vFf6oPSOc051RYa355EM0SYVY9ERV3ZSeagw2sr23rduXhRndNrwzleFbFU0ns79lWaSzpIOf3FGT/KcJ0bi91AOTZ6gtcbevP7rdhPxd3BvQr0FbaKipHN+j7B+euCQwXsYNK668UWPnnwtijRlmi/9eGzaWnqkP4BFHHrkr92BBkIlFdrc5jJP2ELpNng4zhqzxXop2IgLBkWSKZIOyV0u4wL0fQoviltKNmXeUWMrtRu6QRs4FCxYaZpW1Wgf22FZBJuPrRCMtsZ0X4aYtwGnY7zcY3n4T/zFnWJMsCfqOtojCnbDvLQ79Ane/PiKJ/q+gVRfqIn9BvgdnalDKj7SPge9Jdu/8Qa3D8M9ouV6dpuTR+3GoqLRYTQDXzC6VPdvlP5q947fjSNl/S974zyj). ### Context Protocol Vue does not support the context protocol. However, it does have it's own [Provide / Inject API](https://vuejs.org/guide/components/provide-inject.html) that serves an equivalent purpose. Also see [Composition API: Dependency Injection](https://vuejs.org/api/composition-api-dependency-injection.html). ### Scoped Custom Elements Vue does not support scoped custom element registries out of the box. However, after including the correct polyfill, one can create one's own custom element using it's own `CustomElementEegistry` and shadow DOM with Vue components mounted insdie of it. ## React Modern React allows developers to define components as functions that receive properties as their parameters and "return JSX/TSX". React relies far less on transpiling code, with the exception of transforming JSX into a completely different form of a tree of data structures. This allows React to more accurately track changes and only render what needs to be changed instead of "Reconciling" like older versions of React used to do. ### Web Components It is slighly complicated to use [web components/custom elements with React](https://react.dev/reference/react-dom/components#custom-html-elements). The first steps are simple: import the components, register them globally, and then begin using them normally. There is one problem with them in that to access their properties of class methods, one needs to [use a Ref](https://react.dev/learn/manipulating-the-dom-with-refs). This does introduce some hassle, since one needs to be aware of what point in the rendering lifecycle one is in before using the `Ref`. Sometimes this means defering things a very specific manner. For more ease of use, on sould use the [@lit/react wrapper](https://www.npmjs.com/package/@lit/react) to make using web components in React a little easier. Additionally, there are other issue which may present themselves when passing props. See the [React + Custom Elements test results from Custom Elements Everywhere](https://custom-elements-everywhere.com/libraries/react/results/results.html). Fortunately, all these issues will be fixed in the next major version of React. As far as using React inside of a web component, it is doable. One needs to take care to not have multiple versions of React running on the same page. Other than that, React does impose a very strict lifecycle and sequence of events on a component. However, well written components will likely implement a similar lifecycle. ### Shadow DOM React does not use the shadow DOM. However, if someone were to mount a React app inside a shadow DOM then it would be using a shadow DOM. ### Properties/Attributes Properties are just values passed into functions. Since React doesn't use custom elements, DOM attributes are not used at all. Any set of props that are passed into a React function component are just referenced by the structure constructed by the JSX rewriting. If a web component were to use React inside, it would liekly need a special React hook that uses `useSyncExternalStore` to provide access to values provided by properties through an intermediary store. ### Slots React doesn't use slots. It does have a `children` prop, which allows the nested structure to be equivalently passed through functional components. Any slots used by a custom element that uses React would be passed through the DOM. But it would only work if the slot was used in a component. ### Events React uses normal DOM events, but it has a special syntax for handling events. Custom elements may want to use the @lit/react wrapper mentioned above to help properly wire through events. ### Context Protocol React has a method of deeply passing values without using prop drilling. However, the concept of a [React Context](https://react.dev/learn/passing-data-deeply-with-context) is unrelated to the context protocol used by custom elements. However, it shares similarities with the Vue context. If a custom element used React, it could very easily implement the context protocol and provide the values through a custom React hook. ### Scoped Custom Elements Out of the box, React doesn't support scoped custom elements. One would have to use vanilla JS or Lit in order to create an element with a shadow DOM that has it's own custom element registry. Then the React code could mount itself to the shadow DOM and use custom elements as needed. Of course to more easily use those components, it might be easier to just wrap them with @lit/react and then use them. [Make an example and see how this works.] ## Other Frameworks See [Custom Elements Everywhere](https://custom-elements-everywhere.com/) and read through the framework's source to see how it works.