Build custom elements with Vue
Web Components is a collective name for a set of web native APIs that allow developers to create reusable custom elements.
The main benefit of custom elements is that they can be used in any framework, even in scenarios where they are not used. They are ideal when your end users may be using different front-end technology stacks, or when you want to decouple the final application from the details of the component implementation it uses.
Vue and Web Components are complementary technologies, and Vue provides excellent support for the use and creation of custom elements. You can integrate custom elements into existing Vue applications, or use Vue to build and distribute custom elements.
Vue Get 100% score in the Custom Elements Everywhere test. Using custom elements in a Vue app is basically the same as using native HTML elements, but requires some extra configuration to work:
Skip component parsing
By default, Vue takes any non-native HTML tags as Vue components first, while Rendering a Custom Element as a fallback option. This causes Vue to throw a "parsing component failed" warning during development.
- To let Vue know that a specific element should be considered a custom element and skip component parsing, we can specify this option, setting the value on this option object will be used in the browser for template compilation and will affect all components of the configured application.
- In addition, these options can also be overwritten on a per component basis through the compilerOptions option (with higher priority for the current component).
Because it is a compile-time option, the build tool needs to pass the configuration to @vue/compiler-dom:
- vue-loader: Passed through the compilerOptions loader option.
// = { chainWebpack: config => { .rule('vue') .use('vue-loader') .tap(options => ({ ...options, compilerOptions: { // Treat all tag names with ion- as custom elements isCustomElement: tag => ('ion-') } })) } }
- vite: Passed through the option of @vitejs/plugin-vue.
// import vue from '@vitejs/plugin-vue' export default { plugins: [ vue({ template: { compilerOptions: { // Treat all label names with short horizontal lines as custom elements isCustomElement: (tag) => ('-') } } }) ] }
- Configuration when compiling within the browser.
// src/ // It works only when compiled in the browserconst app = createApp(App) = (tag) => ('-')
Passing DOM properties
Since the DOM attribute can only be string values, we can only use the properties of the DOM object to pass complex data. When setting props for custom elements, Vue 3 will passin
The operator automatically checks whether the property already exists on the DOM object, and when this key exists, it is more inclined to set the value to the property of a DOM object. This means that, in most cases, if the custom element followsRecommended best practices, you don't need to consider this issue.
However, there are some special cases: the data must be passed in the form of a DOM object attribute, but the custom element cannot correctly define/reflect this attribute (becausein
The inspection failed). In this case, you can force av-bind
Bind, pass.prop
Modifier to set the properties of the DOM object:
<my-element :="{ name: 'jack' }"></my-element> <!-- Equivalent abbreviation --> <my-element .user="{ name: 'jack' }"></my-element>
defineCustomElement()
Vue provides a defineCustomElement method that is almost exactly the same as that of the general Vue components to support the creation of custom elements. The parameters received by this method are exactly the same as defineComponent. But it returns a constructor that inherits from the native custom element class (can be registered with ()).
function defineCustomElement( component: | (ComponentOptions & { styles?: string[] }) | ComponentOptions['setup'] ): { new (props?: object): HTMLElement }
In addition to the regular component options, defineCustomElement() also supports a special option styles, which is an array of inline CSS strings, and the provided CSS is injected into the ShadowRoot of that element.
<my-vue-element></my-vue-element>
import { defineCustomElement } from 'vue' const MyVueElement = defineCustomElement({ // Here are the same Vue component options as usual props: {}, emits: {}, template: `...`, // defineCustomElement unique: CSS injected into ShadowRoot styles: [`/* css */`] }) // After registering a custom element, all `<my-vue-element>` tags on this page will be upgraded('my-vue-element', MyVueElement) // It is also possible to instantiate elements after registration:( new MyVueElement({ // Initialize props (optional) }) )
If the console reports an error at this time: \color{red}{If the console reports an error at this time: } If the console reports an error at this time: Component provided template option but runtime compilation is not supported, add the following configuration in :
resolve: { alias: { 'vue': 'vue/dist/' } },
life cycle
- When the connectedCallback of the element is first called, a Vue custom element will internally mount a Vue component instance to its ShadowRoot.
- When the disconnectedCallback of this element is called, Vue checks whether the element remains in the document after a microtask.
- If the element is still in the document, it means it is a move operation and the component instance will be preserved;
- If the element no longer exists in the document, it means that this is a removal operation and the component instance will be destroyed.
Props
- All props declared using the props option are defined as attributes on that custom element. Vue automatically and appropriately handles reflections of an attribute or attribute.
- attribute is always reflected to the corresponding attribute type as needed. The attribute values (string, boolean, or number) of the base type are reflected as attribute.
- When they are set to attribute (always a string), Vue will automatically convert props declared with the Boolean or Number type to the desired type. For example, the following props statement:
props: { selected: Boolean, index: Number }
And use custom elements in the following way:
<my-element selected index="1"></my-element>
In the component, selected will be converted to true (boolean type value) and index will be converted to 1 (number type value).
event
- Events triggered by emit are distributed from custom elements in the form of CustomEvents.
- The additional event parameters (payload) will be exposed as a detail array on the CustomEvent object.
Slot
- In a component, the slot will be rendered as usual. However, when the final element is used, it only accepts the syntax of native slots and does not support scoped slots.
- When passing named slots, you should use the slot attribute instead of the v-slot directive:
<my-element> <div slot="named">hello</div> </my-element>
Dependency injection
- The Provide / Inject API and the corresponding combined API work properly in custom elements defined by Vue.
- However, dependencies only work between custom elements. For example, a custom element defined by Vue cannot inject a property provided by a regular Vue component.
Compile SFC into a custom element
defineCustomElement can also be used with Vue Single File Component (SFC). However, according to the default toolchain configuration, the<style>
It is still extracted and merged into a separate CSS file when it is built in production. When writing custom elements using SFC, it usually needs to be injected instead<style>
Tags to ShadowRoot of the custom element.
The official SFC toolchain supports importing SFC in "custom element mode" (requires @vitejs/plugin-vue@^1.4.0 or vue-loader@^16.5.0). An SFC loaded in custom element mode will inline its<style>
Tags as CSS strings and exposes them as the styles option of the component. This is extracted and used by defineCustomElement and injected on the element's ShadowRoot at initialization.
To enable this mode, end the component file with .:
// <template> <h1></h1> </template> <script> </script> <style> h1 { color: red; } </style>
import { defineCustomElement } from 'vue' import Example from './' () // Convert to custom element constructorconst ExampleElement = defineCustomElement(Example) // register('my-example', ExampleElement)
Build a custom element library based on Vue
Export constructors by elements so that users can flexibly import them on demand, and can also export a function to facilitate users to automatically register all elements.
// Vue custom element library entry fileimport { defineCustomElement } from 'vue' import Foo from './' import Bar from './' const MyFoo = defineCustomElement(Foo) const MyBar = defineCustomElement(Bar) // Export elements separatelyexport { MyFoo, MyBar } export function register() { ('my-foo', MyFoo) ('my-bar', MyBar) }
defineComponent()
A helper function used to provide type derivation for TypeScript when defining Vue components.
- For a ts file, if we write export default {} directly, we cannot prompt in a targeted manner what attributes should be in the vue component.
- However, if you add a layer of defineComponet, export default defineComponent({}), you can perform some type derivation and attribute prompts on parameters.
function defineComponent( component: ComponentOptions | ComponentOptions['setup'] ): ComponentConstructor
A parameter is a component option object. The return value will be the option object itself, because the function actually has no operation at runtime and is only used to provide type derivation. Note that the type of return value is a bit special: it is a constructor type, which is the component instance type inferred from the option. This is to allow this return value to provide type derivation support when used as a tag in TSX.
You can do this fromdefineComponent()
The return type extracts the instance type of a component (with its optionsthis
The type of equivalent):
const Foo = defineComponent(/* ... */) type FooInstance = InstanceType<typeof Foo>
defineAsyncComponent()
Used to define an asynchronous component. In large projects, we may need to split the application into smaller chunks and load relevant components from the server only if needed. defineAsyncComponent is lazy to load at runtime, and the argument can be an asynchronous loading function that returns a Promise (the resolve callback method should be called when obtaining component definitions from the server), or an option object that makes a more specific customization of the loading behavior.
import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => { return new Promise((resolve, reject) => { // ...get components from the server resolve(/* The obtained component */) }) }) // ... Use `AsyncComp` like other general components// You can also use the ES module to import dynamicallyconst AsyncComp = defineAsyncComponent(() => import('./components/') )
The resulting AsyncComp is an outer-wrapped component that calls the function that loads the internal actual component only when the page needs it to render. It passes the received props and slots to the internal components, so you can seamlessly replace the original components with this asynchronous wrapper component while implementing lazy loading.
Like normal components, asynchronous components can be registered globally using ():
('MyComponent', defineAsyncComponent(() => import('./components/') ))
You can also define them directly in the parent component:
<script setup> import { defineAsyncComponent } from 'vue' const AdminPage = defineAsyncComponent(() => import('./components/') ) </script> <template> <AdminPage /> </template>
Asynchronous operations inevitably involve loading and error states, so defineAsyncComponent() also supports handling of these states in advanced options:
const AsyncComp = defineAsyncComponent({ // Loading function loader: () => import('./'), // Components used when loading asynchronous components loadingComponent: LoadingComponent, // Display the delay time before loading the component, default is 200ms delay: 200, // Components displayed after loading failed errorComponent: ErrorComponent, // If a time limit is provided and timeout is exceeded, the error-reported component configured here will also be displayed. The default value is: Infinity timeout: 3000 })
The above is the detailed explanation of using defineCustomElement to define components in Vue3. For more information about defining components in Vue3, please follow my other related articles!