Использование Vue.js для создания пользовательских Web компонентов
Перевод статьи Simon Tarchichi "Use Vue.js to create custom web components". Автор делает краткий обзор Web components и показывает как их можно создавать с помощью Vue.js.
Web компоненты позволяют определять новые HTML теги, которые принято называть пользовательскими элементами (custom elements). Эти теги могут быть в дальнейшем использованы непосредственно в HTML коде вашего приложения:
<div id="my-app"> <p>Introduction text</p> <share-buttons/></div>
В этом примере, будет интерпретирован браузером и "заменен" HTML разметкой, которую вы для него определили. В результате получим:
<div id="my-app"> <p>Introduction text</p> <share-buttons> <div id="share-buttons"> <a href="#">Facebook</a> <a href="#">Twitter</a> </div> </share-buttons></div>
Так же здесь можно было бы использовать JS логику, чтобы при событии click делиться страницей через Facebook или Twitter.
Web компоненты подобны Vue.js компонентам. Они имеют жизненный цикл (lifecycle), свойства и могут быть вложенными. Их API менее мощный, но зато следует стандарту, определенному в W3C спецификации.
Проблема в том, что Web компоненты не имеют полной поддержки в браузерах до настоящего момента. Посмотреть поддержку Web компонентов браузерами можно на сайте are-we-componentized-yet или caniuse.com (*).
Но, применив немного JS магии вы можете прямо сейчас перевести ваши Vue.js компоненты в Web компоненты, что позволит использовать их в любых web приложениях, даже вместе с React, Angular или .
Как получить из вашего Vue.js компонента универсальные Web компоненты
Понадобится vue-custom-element библиотека написанная @karol-f. Она позволит вам использовать Vue.js компонент как пользовательский элемент.
HTML подход
Один раз зарегистрировав пользовательский компонент вы можете вставлять его тег в обычный HTML, SPA, React, Angular или Vue.js проект.
<div id="my-app"> <p>Introduction text</p> <share-buttons gplus="false"/></div> <template id="share-buttons-template"> <div id="share-buttons"> <a href="#" @click.prevent="share" v-if="facebook">Facebook</a> <a href="#" @click.prevent="share" v-if="twitter">Twitter</a> <a href="#" @click.prevent="share" v-if="gplus">Google+</a> </div></template> <script src="vue.min.js"></script><script src="vue-custom-element.min.js"></script><script> 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); } } });</script>
Данный подход требует включения в вашу страницу ядра Vue.js и библиотеки vue-custom-element.
Props будут автоматически интерпретированы к их нативным типам (атрибут "false" интерпретируется как boolean значение false).
API пользовательского элемента доступно так же как любого другого HTML элемента: document.getElementsByTagName(''share-buttons'').
Пример выше использует HTML тег , но вы можете поместить строку шаблона непосредственно в template свойство вашего компонента.
Плюсы и минусы
Из вышеописанного подхода следует что:
зависимый скрипт должен быть включен в итоговый HTML файл
поведение компонента можно прочитать непосредственно из исходного кода страницы
Это может оказаться как плюсами, так и минусами в зависимости от ваших задач.Наиболее вероятный подход, который я могу представить - это распространение Vue.js компонентов в форме виджетов на множество вебсайтов. Итак, давайте соберем весь этот код в единый файл.
Сборка Vue.js компонентов в единый .js файл
Посмотрите на vue-customelement-bundler репозиторий, который содержит:
настройки Webpack
код компонента (ES2015 во .vue файле)
NPM зависимости, чтобы можно было собрать и запустить собственный экземпляр
Он позволяет взять код Vue.js компонента (в форме .vue файла) и на выходе получить единый .js файл с вложенным кодом самого Vue, библиотеки vue-custom-element и вашего Vue.js компонента. Получившийся файл будет зарегистрирован, чтобы использоваться, как пользовательский элемент. Далее, можно использовать ваш компонент в любом HTML/JS приложении, подобно этому:
<html> <body> ... <my-vue-component/> <!-- allows multiples instances of the same component --> <my-vue-component my-prop="true"/> <script src="my-vue-component.js"></script> </body></html>
Замечание: Итоговый файл весит 266kB (предыдущий текст перечеркнут). Слишком большой размер файла. Мои навыки в работе с Webpack не позволили его уменьшить. Попытки ипользовать плагин UglifyJsPlugin привели к ошибке, которую не удалось устранить. Возможно, у вас получиться лучше, пожалуйста сообщите мне как можно оптимизировать мои настройки, уверен их можно улучшить.
Изменение 5/4: Спасибо @anthonygore и UglifyJS, итоговый файл стал весить 113kB (предыдущий текст перечеркнут).
Изменение 6/4: Энтони удалось уменьшить сборку еще на 22kB. Итоговый файл теперь весит 91kB (32kB в gzip)!
Изменение 11/4: @chimon2000 предложено в качестве альтернативы для сборки использовать rollup.js, таким образом, вес файла стал 76kB (24kB в gzip), спасибо Раян! Эта сборка находится в rollup ветке репозитория.
Немного об API Web компонентов (Web Components API)
Это часть статьи не обязательна для использования Vue.js компонентов, как Web компонентов, но часто бывает полезно знать как это работает, чтобы писать код лучше.
Web компоненты включают следующие W3C спецификации:
Custom Element
HTML Template
HTML Import
Рассмотрим каждую из них подробнее.
Custom Element
Custom Element является основным API, доступным разработчикам для определения новых HTML тегов, которые могут быть интерпретированы браузером.
Функции обратных вызовов жизненного цикла (иначе говоря, реакции):
constructor (элемент обновлен, имеется ввиду вставлен в DOM с помощью JS или изначально там находился)
connectedCallback (вставка в DOM)
disconnectedCallback (удаление из DOM)
adoptedCallback (перемещение в новый документ)
attributeChangedCallback
Звучит знакомо? Да, это выглядит как жизненный цикл компонентов Vue.js!Однако, написание пользовательских элементов намного более многословно. Одна из причин этого - вы не можете получить выгоду от магии реактивных свойств во Vue.js, или "сахара" в синтаксисе шаблонов типо v-if выражений.
Регистрация пользовательских элементов выполняется используя 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);
Обратите внимание, что connectedCallback вызывается, когда элемент уже вставлен.
Помимо этого, теги компонентов должны соответствовать нескольким требованиям:
содержать дефис
не содержать заглавных символов
не использовать одно из запрещенных имен (annotation-xml, color-profile и т.д.)
HTML Template
Разметка пользовательского элемента может содержаться в теге внутри DOM.
<template id="share-buttons-template"> <div id="share-buttons"> <a href="#">Facebook</a> <a href="#">Twitter</a> </div></template>
Эта разметка может быть импортирована и клонирована в пользовательский элемент по реакции на createdCallback.
HTML Import
Определяет разметку и JS логику в одном файле, который может быть импортирован в ваше приложение с помощью одного тега.
<html> <head> <link rel="import" href="share-buttons.html"> ... </head> <body> ... <share-buttons></share-buttons> ... </body></html>
Содержимое файла share-buttons.html:
<html> <template> <div id="share-buttons"> <a href="#">Facebook</a> <a href="#">Twitter</a> </div> </template> <script> (function() { ... document.registerElement(''share-buttons'', { prototype: MyCustomElement }); }); </script></html>
Звучит все более и более знакомо, не так ли?
(**)
Взаимодействие компонентов
Взаимодействие компонента с его родителем происходит посредством событий, точно так же как во Vue.js. События запускаются используя dispatchEvent метод HTML элементов.
this.dispatchEvent(new Event(''content-shared''));
Родительский компонент может взаимодействовать с дочерними двумя способами:
используя атрибуты
используя методы компонента прототипа
Первый способ точно такой же как во Vue.js. Вы можете установить атрибуты, только они не будут реактивными, так что вам будет нужно использовать attributeChangedCallback если захотите запускать логику, когда заначение атрибутов меняется.
Второй метод тоже работает во Vue.js, хотя там он и не рекомендуется.
Поддержка и полифиллы (polyfills)
Состоянии поддержки браузерами пользовательских компонентов можно узнать на caniuse.com или are-we-componentized-yet.
Библиотека полифиллов позволяет вам использовать эти API в браузерах, еще не имеющих их поддержки. Ниже ссылки на некоторые из них:
Custom Elements polyfill (от команды Polymer)
document-register-element (от Andrea Giammarchi)
Замечание: я не упоминал Shadow DOM в этой статье. Думаю это могло бы смутить разработчиков, которые фокусируются на переносе их Vue.js компонентов к пользовательским элементам. Посмотрите ссылки в конце, чтобы получить больше информации по данной теме.
Ссылки
Vue Custom Element: трансформация Vue.js компонентов в пользовательские элементы.
Vue Custom Element Bundler: сборка Vue.js компонентов в виде единого .js файла.
WebComponents.org: каталог и ресурсы по пользовательским элементам (Custom Elements).
Статья "The case for Custom Elements" (часть 1 и часть 2) автор Rob Dodson.
Demythstifying Web Components автор Daniel Buchner.
Примечания к переводу
* На начало августа 2019 года все современные браузеры начали поддерживать актуальные спецификации Web Components. Исключением можно назвать MS Edge, но стоит учитывать, что Microsoft отказалась от дальнейшего развития собственной архитектуры и уже планируется выход Edge на основе Chromium.
** Со времени написания и перевода статьи спецификации претерпели ряд изменений. Актуальную информацию можно найти на сайте webcomponents.org.
*** К сожалению, ссылка на оригинал недоступна, автор удалил сайт.