@telia/vue-web-component-wrapper-ie11
Wrap and register a Vue component as a custom element (with Shadow DOM), in IE 11 and beyond.
Features
@vue/web-component-wrapper:
From the original project:- Automatically emits Custom Events from
this.$emit()
calls - Supports passing in slots
- Isolated styles, thanks to Shadow DOM (has limitations with leaking styles in IE 11, see this section)
- Very simple to use.
From this fork
- Best-effort IE 11 support thanks to ES5 transpilation, a work-around to fix a bug in ShadyDOM regarding Mutation Observers, and a few other fixes specifically written for IE 11 support. Just make sure to include the polyfill described in the next section.
- If enabled, offers auto-injection of global styles. Can be configured to be selective, too.
- If enabled, exposes "-json" suffixed props based on your source component props, that automatically map JSON to Objects/Arrays back to your source props.
- Better support for Vue's scoped style tags without FOUC's.
Usage
import '@webcomponents/webcomponentsjs'
import Vue from 'vue'
import wrap from '@telia/vue-web-component-wrapper-ie11'
import VueComponent from './VueComponent.vue'
const CustomElement = wrap(Vue, VueComponent, {
// globalStyles: true,
// jsonMapping: true,
})
window.customElements.define('vue-component', CustomElement)
Now you can just add <vue-component>
anywhere in the page, and the component will appear fully loaded, like it was made by the browser itself. This seamless step is arguably the best part of utilizing Web Components.
It works with async components as well - you can pass an async component factory function that returns a Promise, and the function will only be called when an instance of the custom element is created on the page:
const CustomElement = wrap(Vue, () => import(`VueComponent.vue`))
window.customElements.define('my-element', CustomElement)
You can also import or just have a <script>
tag point to @telia/vue-web-component-wrapper-ie11/dist/vue-wc-wrapper.global.js
, which will expose wrap
as wrapVueWebComponent
globally on the window object.
How do I use this instead of the standard library shipped with Vue-CLI?
- Change from
--target wc
to--target lib
(or anything else really) in the Vue-CLI run options (probably in package.json)- Add
--inline-vue
if you want to make sure Vue is included in your web component.
- Add
- Make sure you have a main.js file.
- Import and use the
wrap()
method as shown above - You can also export the bare Vue component also via ES6 exports, should you need to.
Note that with the above approach, you'll be putting all web components in a single bundle. This section does not document how to make a bundle per web component.
Polyfilling for IE 11
Easiest is to do:
import '@webcomponents/webcomponentsjs'
The polyfill bundle with check if the browser supports web components and will apply polyfills as needed. If you want to reduce bundle size even further, that is possible, just consult the @webcomponents/webcomponentsjs documentation on using their loader.
Whatever you do, do not import the custom elements and other polyfills directly, as it will overwrite native browser functionality for browsers like Chrome, that already support Web Components.
IE11 Warning: Note on CSS Encapsulation When Using the Shady DOM polyfill
The styles will leak through to your web components in IE 11 in several cases, like when passing slots to the web component. Make sure you use a way to encapsulate your styles like BEM, Scoped styles (has drawbacks of its own), or CSS Modules. Choose the one that fits you.
API
wrap(VueInstance, sourceComponent, wrapOptions?)
Returns a Custom Element, that can be used in the browser-provided window.customElements.define
call.
VueInstance
Type: object
Your Vue instance.
sourceComponent
Type: object
Your source component (can also just be a simple javascript object {}
, not a separate vue file).
wrapOptions?
Type: object
supportScopedCss?
Type: boolean
Default: true
Setting this to false will skip a 0ms setTimeout() call, but will also break Vue's scoped attribute support in the form of FOUC's.
globalStyles?
Type: boolean | object
Default: false
If you want auto-injection of the global styles, set it to true
.
You can specify more granular rules for global style injection, refer to this part of the source code for details. The defaults should be reasonable for most developers.
jsonMapping?
Type: boolean
Default: false
If you want automatic deserialization of javascript objects and arrays.
Then just pass "my-array-json" like :my-array-json="JSON.stringify({ a: 1 })"
to your web component, it will parse it to the prop called "myArray" in the vue file as a regular js object again. If the json is invalid it will become "INVALID_JSON". This works for props defined with type Array
and Object
Interface Proxying Details
Props
-
All
props
declared in the Vue component are exposed on the custom element as its properties. Kebab-case props are converted to camelCase properties, similar to how they are converted in Vue. -
Setting properties on the custom element updates the props passed to the inner Vue component.
-
Setting attributes on the custom element updates corresponding declared props. Attributes are mapped to kebab-case. For example, a prop named
someProp
will have a corresponding attribute namedsome-prop
. -
Attributes that map to props declared with
type: Boolean
are auto-casted into boolean values in the following rules:-
""
or same value as attribute name: ->true
-
"true"
->true
-
"false"
->false
-
-
Attributes that map to props declared with
type: Number
are auto-casted into numbers if the value is a parsable number.
Events
Custom events emitted on the inner Vue component are dispatched on the custom element as a CustomEvent
. Additional arguments passed to $emit
will be exposed as an Array as event.detail
.
Slots
Slots work the same way as expected, including named slots. They also update when changed (using MutationObserver
).
Scoped slots however, are not supported as they are a Vue specific concept.
Lifecycle
When the custom element is removed from the document, the Vue component behaves just as if it's inside a <keep-alive>
and its deactivated
hook will be called. When it's inserted again, the activated
hook will be called.
If you wish to destroy the inner component, you'd have to do that explicitly:
myElement.vueComponent.$destroy()
Acknowledgments
Special thanks to the prior work by @karol-f in vue-custom-element.
License
MIT