Использование 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 свойство вашего компонента.

Плюсы и минусы

Из вышеописанного подхода следует что:

Это может оказаться как плюсами, так и минусами в зависимости от ваших задач.Наиболее вероятный подход, который я могу представить - это распространение Vue.js компонентов в форме виджетов на множество вебсайтов. Итак, давайте соберем весь этот код в единый файл.

Сборка Vue.js компонентов в единый .js файл

Посмотрите на vue-customelement-bundler репозиторий, который содержит:

Он позволяет взять код 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

Custom Element является основным API, доступным разработчикам для определения новых HTML тегов, которые могут быть интерпретированы браузером.

Функции обратных вызовов жизненного цикла (иначе говоря, реакции):

Звучит знакомо? Да, это выглядит как жизненный цикл компонентов 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 вызывается, когда элемент уже вставлен.

Помимо этого, теги компонентов должны соответствовать нескольким требованиям:

Подробнее в W3C спецификации.

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 в браузерах, еще не имеющих их поддержки. Ниже ссылки на некоторые из них:

Замечание: я не упоминал Shadow DOM в этой статье. Думаю это могло бы смутить разработчиков, которые фокусируются на переносе их Vue.js компонентов к пользовательским элементам. Посмотрите ссылки в конце, чтобы получить больше информации по данной теме.

Ссылки


Примечания к переводу

* На начало августа 2019 года все современные браузеры начали поддерживать актуальные спецификации Web Components. Исключением можно назвать MS Edge, но стоит учитывать, что Microsoft отказалась от дальнейшего развития собственной архитектуры и уже планируется выход Edge на основе Chromium.

** Со времени написания и перевода статьи спецификации претерпели ряд изменений. Актуальную информацию можно найти на сайте webcomponents.org.

*** К сожалению, ссылка на оригинал недоступна, автор удалил сайт.