If you have any thoughts on my blog or articles and you want to let me know, you can either post a comment below(public) or tell me via this i_kkkp@163.com

Vue Components and Web Components Custom Elements

Preface

Web Components is a collective term for a set of native web APIs that allow developers to create reusable custom elements.

Vue and Web Components are primarily complementary technologies. Whether integrating custom elements into existing Vue applications or building and distributing custom elements using Vue, Vue provides excellent support for both using and creating custom elements.

What are Custom Elements

A key feature of Web Components is the ability to create custom elements: HTML elements whose behavior is defined by web developers, extending the set of elements available in browsers.

There are two types of custom elements in Web Components:

  • Customized Built-in Elements: Inherit from standard HTML elements, such as HTMLImageElement or HTMLParagraphElement. Their implementation defines the behavior of standard elements.
  • Autonomous Custom Elements: Inherit from the HTML element base class HTMLElement. You have to implement their behavior from scratch.

Custom Element Lifecycle Callbacks

Custom Elements also have lifecycle callbacks.

Once your custom element is registered, the browser calls certain methods of your class when your custom element interacts with the page in specific ways. By providing implementations for these methods, known as lifecycle callbacks, you can run code to respond to these events.

The custom element lifecycle callbacks include:

  • connectedCallback(): Called whenever the element is added to the document. The specification recommends developers to set up custom element properties in this callback rather than in the constructor.
  • disconnectedCallback(): Called whenever the element is removed from the document.
  • adoptedCallback(): Called whenever the element is moved to a new document.
  • attributeChangedCallback(): Called when attributes are changed, added, removed, or replaced. For more detailed information about this callback, see Responding to attribute changes.

Here is a minimal example of a custom element that logs these lifecycle events:

// Create a class for this element
class MyCustomElement extends HTMLElement {
  static observedAttributes = ["color", "size"];

  constructor() {
    // Must call the super method first
    super();
  }

  connectedCallback() {
    console.log("Custom element added to the page.");
  }

  disconnectedCallback() {
    console.log("Custom element removed from the page.");
  }

  adoptedCallback() {
    console.log("Custom element moved to a new page.");
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Attribute ${name} has changed.`);
  }
}

customElements.define("my-custom-element", MyCustomElement);

Using Custom Elements in Vue

Using custom elements in a Vue application is largely similar to using native HTML elements, but a few points need to be kept in mind:

Skipping Component Resolution

By default, Vue tries to parse non-native HTML tags as registered Vue components and then render them as custom elements. This leads to Vue issuing a “Unknown custom element” warning during development. To inform Vue that certain elements should be treated as custom elements and skip component resolution, we can specify the compilerOptions.isCustomElement option.

If you are using Vue for build setup, this option should be passed through build configuration as it is a compile-time option.

Example of in-browser configuration:

// Only works if using in-browser compilation.
// If using build tools, see config examples below.
app.config.compilerOptions.isCustomElement = (tag) => tag.includes('-')

Vite Configuration Example

// vite.config.js
import vue from '@vitejs/plugin-vue'

export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // treat all tags with a dash as custom elements
          isCustomElement: (tag) => tag.includes('-')
        }
      }
    })
  ]
}

Vue CLI Configuration Example

// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => ({
        ...options, 

        
        compilerOptions: {
          // treat any tag that starts with ion- as custom elements
          isCustomElement: tag => tag.startsWith('ion-')
        }
      }))
  }
}

Building Custom Elements with Vue

The primary advantage of custom elements is their ability to be used with any framework or even without a framework. This makes them suitable for distributing components to end consumers who may not use the same frontend stack, or when you want to isolate the implementation details of the components used in the final application.

Defining Custom Elements

Vue supports creating custom elements using the same Vue component API with the defineCustomElement method. This method takes the same parameters as defineComponent but returns an extended custom element constructor HTMLElement:

Template:0

<my-vue-element></my-vue-element>
import { defineCustomElement } from 'vue'

const MyVueElement = defineCustomElement({
  // normal Vue component options here
  props: {},
  emits: {},
  template: `...`,

  // defineCustomElement only: CSS to be injected into shadow root
  styles: [`/* inlined css */`]
})

// Register the custom element.
// After registration, all `<my-vue-element>` tags
// on the page will be upgraded.
customElements.define('my-vue-element', MyVueElement)

// You can also programmatically instantiate the element:
// (can only be done after registration)
document.body.appendChild(
  new MyVueElement({
    // initial props (optional)
  })
)

When discussing custom elements and Vue components, we are essentially talking about two different approaches to building web applications. Custom elements are a web standard, akin to HTML elements, while Vue components are a more advanced building approach provided by the Vue.js framework.

Some argue that using only custom elements is a more “future-proof” approach, but this passage points out that such a view is overly simplistic. It lists reasons explaining why the Vue component model is more practical. Some key points include:

Vue components offer more features, such as a convenient template system, methods for managing state, and an efficient way to render components on the server. These features are essential for building complex applications.

Vue components support powerful composition mechanisms, while custom elements have some limitations in this regard. This means that with Vue, you can build flexible and powerful component structures more easily. Using Vue, you leverage a mature framework and a large community, without having to build and maintain an internal framework.

There is indeed some overlap in functionality between custom elements and Vue components: both allow us to define reusable components with data passing, event emitting, and lifecycle management. However, the Web Components API is relatively lower-level and simpler. To build actual applications, we need some additional features not covered by the platform:

  • Declarative and efficient template system;
  • A reactive state management system conducive to extracting and reusing logic across components;
  • A high-performance method for rendering components on the server (SSR) and composing them on the client side, crucial for Web Vitals metrics like SEO and LCP. Native custom elements SSR typically involves simulating the DOM in Node.js and then serializing the mutated DOM, while Vue SSR compiles as much as possible into string concatenation, which is more efficient.

defineCustomElement API Vue Component Transformation

Using the defineCustomElement API to transform Vue components into registerable custom element classes has several benefits:

  1. Cross-Framework Integration: By transforming Vue components into custom element classes, you can use these components in different frontend frameworks and libraries. This makes your components more versatile and integrable with other technology stacks.

  2. Independent Usage: After registering a Vue component as a custom element, it can be used independently of a Vue application. This means you can use the component without an entire Vue application, and introduce it into different build systems and module systems.

  3. Progressive Migration: If your application is gradually transitioning to Vue, you can achieve a progressive migration by transforming certain components into custom elements. This allows you to gradually introduce Vue components into an existing project without the need for a complete rewrite.

  4. Web Components Standard Compatibility: Registering Vue components as custom elements makes them compatible with the Web Components standard. This means you can leverage other tools and libraries in the Web Components ecosystem, enhancing the interoperability of your components.

In other words, the defineCustomElement API’s purpose is to compile Vue components into custom elements that can be used in the browser without relying on the Vue compiler for real-time compilation.

When using the defineCustomElement API, Vue components are pre-compiled into native custom elements, allowing them to be used directly in the browser without runtime compilation.

Overall, by using the defineCustomElement API, you can combine Vue components with custom elements, making these components usable and shareable in a broader context. This provides increased reusability and flexibility for components, especially in the context of cross-platform component development, where you can develop components as custom elements and use them in different environments. A typical example is the integration implementation mentioned earlier between Vue 2 and Vue 3, meaning you only need to compile Vue 3 components into custom elements and then use them in Vue 2.

Bubbling and Capturing Approach for Coexistence Development of Vue 2/3

Comments