Create Vue 3.2 Custom Components
This project was created based on the post Create a Vue Library. Please review that post before this. There're some differences in terms of vite build config, element file name, and build entry file. Let's start!
Update vite config to support building customElement
vite.config.ts
add build for custom elements option.
// ...
export default defineConfig({
plugins: [
vue({
+ customElement: true,
+ template: {
+ compilerOptions: {
+ // treat all tags with a dash as custom elements
+ isCustomElement: (tag) => tag.startsWith('dv-'),
+ },
+ },
}),
],
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'DvComponents',
fileName: (format) => `dv-ce.${format}.js`,
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['vue'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: 'Vue',
},
},
},
},
});
Create Vue Web Component
Vue 3.2 supports to create custom element by add a ce
postfix, short for custom element. So our component names should
be changed to card.ce.vue
. Let's create components/card.ce.vue
. The content is the same as a normal vue component
except that tailwind won't work due to shadowDom.
<template>
<!-- rounded-lg bg-white not working! -->
<div class="dv-card rounded-lg bg-white">
<header class="dv-card-header">
<slot name="header"></slot>
</header>
<div class="dv-card-body">
<slot name="body"></slot>
</div>
<footer :class="['dv-card-footer', isFooterBordered ? 'border-t' : '']">
<slot name="footer"></slot>
</footer>
</div>
</template>
<script setup lang="ts">
import {ref, toRefs, reactive} from 'vue';
defineProps({
isFooterBordered: {
default: false,
},
});
</script>
<style scoped>
.dv-card {
background: yellowgreen;
border: 1px solid red;
}
.border-t {
border-top: 1px solid gray;
}
</style>
<script lang="ts">
export default {
name: 'DvCard',
inheritAttrs: false,
customOptions: {},
};
</script>
Tailwind won't work.
rounded-lg bg-white
these tailwind class are just used to test. In real Vue custom elements don't import tailwind, we need to define class due to shadowDom.
Then let's export ce components in components/index.ts
:
export {default as Card} from './card.ce.vue';
Register Web component
Let's register the web component at the entry file src/index.ts
:
// tailwind css WON'T work, delete it.
import '@/styles/index.css';
import {defineCustomElement} from 'vue';
import {CurrentTime, Card} from './components';
// 1. The essential part: Vue transform vue component to web component
const CurrentTimeComponent = defineCustomElement(CurrentTime);
const CardComponent = defineCustomElement(Card);
// 2. register web component as usual
customElements.define('dv-current-time', CurrentTimeComponent);
customElements.define('dv-card', CardComponent);
Now we can successfully build the dist.
Verify Web Components
Solution 1: import the dist in index.html
. Note that if any change, we have to rebuild.
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
+ <script type="module" src="/dist/dv-ce.es.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Solution 2 (recommended): Storybook, refer this storybook link.
Solution 3 (quick test): import the entry file to App.vue
:
<template>
<h2 class="text-2xl">Current Time</h2>
<dv-current-time>
<span slot="prefix">Time is</span>
</dv-current-time>
<h2 class="text-2xl">Card</h2>
<dv-card :is-footer-bordered="true">
<div slot="header">Header</div>
<div slot="body">body</div>
<div slot="footer">footer</div>
</dv-card>
</template>
<script setup lang="ts">
// import the entry file since it has the web component registration
import '@/index';
</script>
<style scoped>
.dv-card {
background: #a2b8ff;
}
</style>
If the Vue web component has slots, we can use this syntax <span slot="prefix">Time is</span>
.
More detail about web component slot can be founded here.
Again, due to shadowDom, the class
.dv-card
defined in App.vue won't work.
Final Thoughts about web component
Vue official website explained a lot about the comparison between Vue component vs Web component. They also has a tip on building Vue-powered web component. Based on the investigation of all 61 ways to create web component, Vue is not a good option in terms of performance and size. You can click this link to see the detail of creating web components.
I tend to learn lit and svelte
for web components tools. lit
is used by VMware Clarity
Core.