Web Components let you define new HTML tags, referred to as custom elements. These tags can then be used in your app’s HTML code directly, like this :
id="my-app">
Introduction text
In this example,
will be interpreted by the browser and “replaced” by the HTML markup you have defined. This will result in :
It may also include custom JS logic, for instance the Facebook and Twitter links could listen to click
events and share the current page when a link is clicked.
Web components are similar to Vue.js components. They have a lifecycle, properties, and can be nested. They have a different API that is less powerful but standard, defined by W3C specs.
Problem : web components are not fully supported by browsers yet. See browser support for Web Components on are-we-componentized-yet or caniuse.com
But, with a bit of JS magic you can now turn your Vue.js component into web components, enabling you to use it in any web application, even using React, Angular or
How to turn your Vue.js component into universal web components
vue-custom-element is a library written by that allows you to use a Vue.js component as a custom element.
The HTML way
Once you register the custom element, you can insert its tag into standard HTML, SPA’s, React, Angular or Vue.js projects.
id="my-app">
Introduction text
gplus="false"/>
id="share-buttons-template">
Vue.customElement('share-buttons', {
template: '#share-buttons-template',
props: {
facebook: { type: Boolean, default: true },
twitter: { type: Boolean, default: true },
gplus: { type: Boolean, default: true }
},
methods: {
share ($event) {
window.alert('Share on ' + $event.target.innerHTML);
}
}
});
This implementation requires Vue.js core files and the vue-custom-element
library to be included in your page.
Props are automatically interpreted to their native type (“false” as an attribute is interpreted as the boolean value false
).
The custom element’s API is accessible like any HTML element : document.getElementsByTagName('share-buttons')
.
This example uses an HTML tag but you can pass the template string directly to the
template
property of your component.
Pros and cons
This implies two things :
- script dependencies have to be included in the final HTML file
- the component behavior is readable directly from the source code
These can be good or bad things, depending on your use case.
A common use case I can imagine is distributing a Vue.js component in form of a widget across multiple websites. So let’s bundle all this code in a single file.
Bundle Vue.js components in a single .js file
Check the repository that contains :
- Webpack configuration
- This example component code (ES2015 in a
.vue
file) - NPM dependencies to get your own up and running
It takes Vue.js components code (in form of .vue files) and output a single .js file embedding Vue itself, the vue-custom-element
lib and your Vue.js components, registered to be used as custom elements. You can then use your components in any HTML/JS app, like this :
...
my-prop="true"/>
Note : The output file weights 266kB. This is too much. I have tried to minify it but my Webpack skills don’t go this far. UglifyJsPlugin threw me an error I couldn’t solve. So if you can, please let me know how to optimize my setup, I am sure it could get much better.
Edit 5/4 : Thanks to and UglifyJS, the output file weights 113kB.
Edit 6/4 : Anthony on fire, gets another 22kB off the bundle. The output file now weights 91kB (32kB gzipped) !
Edit 11/4 : offered an alternative using rollup.js, the file weights 76kB (24kB gzipped), thanks Ryan ! Check it out on the of the repo.
A bit about Web Components API
This section is not essential to use Vue.js components as web components but it is often good to know how things work to write better code.
Web Components include the following specs :
Custom Element
Custom Element is the main API that allows developers to define a new HTML tag that can be interpreted by the browser.
It features lifecycle callbacks (aka reactions) :
- constructor itself (element upgraded, meaning inserted in the DOM via JS or already in the DOM)
-
connectedCallback
(inserted in the DOM) -
disconnectedCallback
(removed from the DOM) -
adoptedCallback
(moved into a new document) attributeChangedCallback
Sounds familiar ? Yes, this does look like the Vue.js components lifecycle !
However, writing custom elements is much more verbose. One of the reasons is that you don’t benefit from Vue.js’s reactive properties magic, or the sweet template syntax such as v-if
statements.
Registering a custom element is done by using window.customElements.define
class MyElement extends HTMLElement {
constructor() {
super();
this.msg = 'Hello, World!';
}
connectedCallback() {
this.innerHTML = `<p>${this.msg}</p>`;
}
}
window.customElements.define('my-element', MyElement);
Note that connectedCallback
is called when the element is inserted
Also, the component’s tag has to meet a few requirements :
- contain an hyphen
- not include uppercase letters
- not be one of the restricted names (
annotation-xml
,color-profile
, etc.)
More details in the W3C specs
HTML Template
The custom element’s markup can be embedded in a tag inside the DOM.
id="share-buttons-template">
This markup can be imported and cloned into the custom element on createdCallback
.
HTML Import
Defines HTML markup and JS logic in a single file that can be imported in your app with a single tag.
rel="import" href="share-buttons.html">
...
...
...
Content of share-buttons.html
:
(function() {
...
document.registerElement('share-buttons', { prototype: MyCustomElement });
});
Sounds more and more familiar, doesn’t it ?
Components communication
Component communicate with its host using events, just as in Vue.js. Events are emitted using the dispatchEvent
method of HTML elements.
this.dispatchEvent(new Event('content-shared'));
Host can communicate with the component in two ways :
- using attributes
- using the component prototype’s methods
The first method is the same as in Vue.js. You can set attributes, they just won’t be reactive so you may have to use attributeChangedCallback
of you want to trigger logic when an attribute’s value is changed.
The second method works in Vue.js, although it is not recommended.
Support and polyfills
Current support for these can be followed on caniuse.com or are-we-componentized-yet.
Polyfill libraries allow you to use these APIs in browsers that don’t support them yet. Here are some links :
- (by the Polymer team)
- (by Andrea Giammarchi)
Note : I haven’t mentioned Shadow DOM in this post. I thought it would confuse developers that focus on porting their Vue.js components to custom elements. Check out the links at the end of the post for more resources.
Links
-
Vue Custom Element : transform Vue.js components into custom elements
-
: bundle Vue.js components in a single
.js
file -
WebComponents.org : Custom Elements catalog and resources
-
The case for Custom Elements (Part 1 & Part 2) by Rob Dodson
-
Demythstifying Web Components by Daniel Buchner