diff --git a/src/guide/extras/web-components.md b/src/guide/extras/web-components.md
index 6bf9a5de45..3f0b687fa6 100644
--- a/src/guide/extras/web-components.md
+++ b/src/guide/extras/web-components.md
@@ -220,6 +220,8 @@ If the custom elements will be used in an application that is also using Vue, yo
It is recommended to export the individual element constructors to give your users the flexibility to import them on-demand and register them with desired tag names. You can also export a convenience function to automatically register all elements. Here's an example entry point of a Vue custom element library:
```js
+// elements.js
+
import { defineCustomElement } from 'vue'
import Foo from './MyFoo.ce.vue'
import Bar from './MyBar.ce.vue'
@@ -236,31 +238,289 @@ export function register() {
}
```
-If you have many components, you can also leverage build tool features such as Vite's [glob import](https://vitejs.dev/guide/features.html#glob-import) or webpack's [`require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext) to load all components from a directory.
+A consumer can use the elements in a Vue file,
+
+```vue
+
+
+
+
+
+
+
+```
+
+or in any other framework such as one with JSX, and with custom names:
+
+```jsx
+import { MyFoo, MyBar } from 'path/to/elements.js'
+
+customElements.define('some-foo', MyFoo)
+customElements.define('some-bar', MyBar)
+
+export function MyComponent() {
+ return <>
+
+
+
+ >
+}
+```
+
+### Vue-based Web Components and TypeScript {#web-components-and-typescript}
-### Web Components and TypeScript {#web-components-and-typescript}
+When writing Vue SFC templates, you may want to [type check](/guide/scaling-up/tooling.html#typescript) your Vue components, including those that are defined as custom elements.
-If you are developing an application or a library, you may want to [type check](/guide/scaling-up/tooling.html#typescript) your Vue components, including those that are defined as custom elements.
+Custom elements are registered globally in browsers using their built-in APIs, and by default they won't have type inference when used in Vue templates. To provide type support for Vue components registered as custom elements, we can register global component typings by augmenting the [`GlobalComponents` interface](https://github.com/vuejs/language-tools/blob/master/packages/vscode-vue/README.md#usage) for type checking in Vue templates (JSX users can augment the [JSX.IntrinsicElements](https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements) type instead, which is not shown here).
-Custom elements are registered globally using native APIs, so by default they won't have type inference when used in Vue templates. To provide type support for Vue components registered as custom elements, we can register global component typings using the the [`GlobalComponents` interface](https://github.com/vuejs/language-tools/blob/master/packages/vscode-vue/README.md#usage) in Vue templates and/or in [JSX](https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements):
+Here is how to define the type for a custom element made with Vue:
```typescript
import { defineCustomElement } from 'vue'
-// vue SFC
-import CounterSFC from './src/components/counter.ce.vue'
+// Import the Vue component.
+import SomeComponent from './src/components/SomeComponent.ce.vue'
+
+// Turn the Vue component into a Custom Element class.
+export const SomeElement = defineCustomElement(SomeComponent)
-// turn component into web components
-export const Counter = defineCustomElement(CounterSFC)
+// Remember to register the element class with the browser.
+customElements.define('some-element', SomeElement)
-// register global typings
+// Add the new element type to Vue's GlobalComponents type.
declare module 'vue' {
- export interface GlobalComponents {
- Counter: typeof Counter
+ interface GlobalComponents {
+ // Be sure to pass in the Vue component type here (SomeComponent, *not* SomeElement).
+ // Custom Elements require a hyphen in their name, so use the hyphenated element name here.
+ 'some-element': typeof SomeComponent
}
}
```
+## non-Vue Web Components and TypeScript
+
+Here is the recommended way to define types for any custom elements that are not
+built with Vue (for example third-party custom elements written in other
+frameworks).
+
+> [!Note]
+> People who may want to do this may be writing custom elements for use across
+> frameworks, and are not using Vue for the implementation of the custom elements,
+> but they want the custom elements to be type checked within Vue SFC templates.
+
+> [!Note]
+> This approach is one possible way to do it, but it may vary depending on the
+> framework being used to create the custom elements. Namely, the part that will
+> be important is the use of `$props` and `$emit` in the following example.
+
+Suppose we have a custom element with some JS properties and events defined, and
+it is shipped in a library called `some-lib`:
+
+```ts
+// file: some-lib/src/SomeElement.ts
+
+// Define a class with typed JS properties (or depending on the method of
+// creating elements, we at least need a type definition, not necessarily a
+// class).
+export class SomeElement extends HTMLElement {
+ foo: number = 123
+ bar: string = 'blah'
+
+ lorem: boolean = false
+
+ // This method should not be exposed to template types.
+ someMethod() {
+ /* ... */
+ }
+
+ // ... implementation details omitted ...
+ // ... assume the element dispatches events named "apple-fell" ...
+}
+
+customElements.define('some-element', SomeElement)
+
+// This is a list of properties of SomeElement that will be selected as
+// attributes/props in framework template type checking systems. For example,
+// these properties will be exposed for type checking in Vue SFC templates. The
+// "lorem" and "someMethod" properties will not be included.
+export type SomeElementAttributes = 'foo' | 'bar'
+
+// Define the event types that SomeElement dispatches.
+export type SomeElementEvents = {
+ 'apple-fell': AppleFellEvent
+}
+
+export class AppleFellEvent extends Event {
+ /* ... details omitted ... */
+}
+```
+
+The implementation details have been omitted, but the important part is that we
+have some type definitions that contain two things: prop types, and event types.
+
+Define a type helper that will allow us to easily define the type of a custom
+element (not necessarily made with Vue) for type checking in Vue:
+
+```ts
+// file: some-lib/src/DefineCustomElement.ts
+
+// We can re-use this type helper per each element we need to define.
+type DefineCustomElement<
+ ElementType extends HTMLElement,
+ Events extends EventMap = {},
+ SelectedAttributes extends keyof ElementType = keyof ElementType
+> = new () => ElementType & {
+ // Use $props to define the properties exposed to template type checking. Vue
+ // specifically reads prop definitions from the `$props` type. Note that we
+ // combine the element's props with the global HTML props and Vue's special
+ // props.
+ /** @deprecated Do not use the $props property on a Custom Element ref, this is for template prop types only. */
+ $props: HTMLAttributes &
+ Partial> &
+ PublicProps
+
+ // Use $emit to specifically define event types. Vue specifically reads event
+ // types from the `$emit` type. Note that `$emit` expects a particular format
+ // that we map `Events` to.
+ /** @deprecated Do not use the $emit property on a Custom Element ref, this is for template prop types only. */
+ $emit: VueEmit
+}
+
+type EventMap = {
+ [event: string]: Event
+}
+
+// This maps an EventMap to the format that Vue's $emit type expects.
+type VueEmit = EmitFn<{
+ [K in keyof T]: (event: T[K]) => void
+}>
+```
+
+> [!Note]
+> We marked `$props` and `$emit` as deprecated so that users that use `ref` to
+> get a reference to a custom element will not use these properties, as these
+> properties are for type checking purposes only, and do not actually exist on a
+> custom element instance.
+
+Using the type helper, we can select the JS properties that Vue should use for
+type checking within SFC templates:
+
+```ts
+// file: some-lib/src/SomeElement.vue.ts
+
+import {
+ SomeElement,
+ SomeElementAttributes,
+ SomeElementEvents
+} from './SomeElement.js'
+import type { Component } from 'vue'
+import type { DefineCustomElement } from './DefineCustomElement'
+
+// Add the new element type to Vue's GlobalComponents type.
+declare module 'vue' {
+ interface GlobalComponents {
+ 'some-element': DefineCustomElement<
+ SomeElement,
+ SomeElementAttributes,
+ SomeElementEvents
+ >
+ }
+}
+```
+
+Suppose that `some-lib` builds its source TypeScript files into a `dist/` folder. A user of
+`some-lib` can then import `SomeElement` and use it in a Vue SFC like so:
+
+```vue
+
+
+
+
+ {
+ // The type of `event` is inferred here to be `AppleFellEvent`
+ }
+ "
+ >
+
+```
+
+If an element does not have type definitions, the types of the properties and events can be
+defined in a more manual fashion:
+
+```vue
+
+
+
+
+
+```
+
+Custom Element authors should not automatically export type custom element
+framework type definitions from their libraries, for example they should not
+export them from an `index.ts` file that also exports the rest of the library,
+or else all type definitions for all frameworks will be defined even if a user
+does not use the frameworks, and if users don't have all frameworks installed
+they may see unexpected type errors for invalid module augmentations. Users
+should import the framework-specific type definition file that they need.
+
## Web Components vs. Vue Components {#web-components-vs-vue-components}
Some developers believe that framework-proprietary component models should be avoided, and that exclusively using Custom Elements makes an application "future-proof". Here we will try to explain why we believe that this is an overly simplistic take on the problem.