Custom Elements with Vue Reactivity

The 13th Hamburg Vue.js Meetup was held last week, this time as an online live-stream due to the ongoing COVID-19 restrictions. I was pleased to represent Factorial to share a talk on some insights on a small experiment in building Custom Elements with Vue 3.x’s standalone Reactivity module. Having previously worked with Vue’s own Custom Elements build option, I was keen to re-explore this topic with standalone Reactivity, also spurred on by Evan You’s ~70 lines-of-code experiment. My own question was, “could it really be that simple?”

Vue 3.x Reactivity

Vue’s Reactive utility (previously known as Vue.observable) has been heavily overhauled since the release of Vue 3 late in 2020. Its dependency to ES6 Proxy makes observing state changes a breeze for developers, but also means that Vue 3 drops support for Internet Explorer 11.

Custom Elements

Custom Elements is a foundational Web Components which allows developers to create and extend HTML tags. The main goal of the Custom Elements API is in creating reusable components (an objective shared with front-end libraries and frameworks like Vue, React, or Angular), using web standards: HTML, CSS and JavaScript. In using web standards, the biggest advantage of using Custom Elements is their easy interoperability in other framework specific projects, e.g. sharing the same Custom Elements across Vue or React projects.

In practice, creating vanilla Custom Elements often involves writing lots of boilerplate code, especially when setting up reactive properties and observed attributes. This is something which is solved in some of the wide array of Custom Elements libraries and frameworks available. Addressing the issue of verbose boilerplate code is also an area where standalone Reactivity shines.

The “vue-micro” Experiment

My experiment in creating a framework to build Custom Elements borrows from Vue creator Evan You’s own proof of concept called vue-lit. The goals of my vue-uhtml (“vue-micro”) experiment were to:

  1. Add props validation
  2. Add a <slot> interface
  3. Implement some form of test coverage

The pseudo code below provides an overview of how the Custom Elements API is used to emulate the rendering and reactive features seen in Vue components.

export default ({ name, setup, props }) => {
  customElements.define(
    name,
    class extends HTMLElement {
      static get observedAttributes() {
        // Return a list of observed attribute names
      }

      constructor() {
        // 1. Scaffold reactive props
        // 2. Scaffold slots as reactive object
        // 3. Apply effect to render the template + run hooks
      }

      connectedCallback() {
        // 1. Run beforeMount hook
        // 2. Render template + invoke setup()
        // 3. Run mounted hook
        // 4. Bind template slots to reactive object
        // 5. Validate props
      }

      disconnectedCallback() {
        // Run unmounted hook
      }

      attributeChangedCallback() {
        // Parse, validate and update reactive props
      }
    }
  );
}

[Pseudo code structure of vue-uhml - JavaScript]
View the actual code here.

Here’s an example of vue-uhtml in action combining reactive re-rendering on user input, props validation, and using a named <slot>.

I spent some time researching how Custom Elements could be test-automated. The community driven Open Web Components initiative provides a set of defaults, recommendations and tools to help facilitate web components projects. The @open-wc/testing package combines and configures testing libraries to quickly get up to speed writing tests for Custom Elements. The vue-uhtml repository includes tests for a Custom Element built with vue-uhtml covering lifecycle hooks, user input and props interfaces.

Take-aways and possible future

To conclude the talk, I shared my top three take-aways from this experiment with the Vue.js Hamburg:

  1. Standalone Vue Reactivity is pretty awesome
  2. Custom Elements are not scary
  3. More developers should also get out there and experiment

My learning experience through this process of experimenting with community driven open source software far outweighed the extent of checking off the original goals I had set out. I was encouraged on this point that the second talk of the evening by Thomas Jacob from sum.cumo (“VueGround: Ein Design-Tool und Playground für Vuetify, in Vuetify”) was also in the spirit of experimentation.

Writing user centric documentation and more comprehensive test coverage for vue-uhtml are two areas to focus on before vue-uhtml to be “production ready”. Whether vue-uhtml will be added to the wide selection of Custom Elements frameworks already available remains a question for the wider developer community to engage with.

Many thanks to Vue.js Hamburg and Joe Ray Gregory for organising this online Meetup, and to sum.cumo for the smooth hosting of the live-stream on YouTube. We are indeed looking forward to the day when in-person Vue.js Hamburg meetups will be possible again.

Links

18