Dev (Back & Front)ARTIGO

Web Components interoperável com ES2015/ES6

Web Components é uma especificação recente, que a maioria de vocês já deve ter ouvido falar. Ela é composta por quatro especificações: Custom Elements, Templates, Shadow DOM e HTML Imports.

Web Components é uma especificação recente, que a maioria de vocês já deve ter ouvido falar. Ela é composta por quatro especificações: Custom Elements, Templates, Shadow DOM e HTML Imports.

Especificamente:

  • Custom Element: permite que você criei seus próprios elementos personalizados ou estenda elementos existentes;
  • Templates: você consegue ter blocos de código que você pode reutilizar;
  • Shadow DOM: permite que você faça o encapsulamento do seu código;
  • HTML Imports: permite que você carregue seus elementos na sua página.

Outra coisa recente é o ES2015/ES6, a nova especificação da EcmaScript, que vem cheia de features legais. E essas duas especificações podem trabalhar muito bem juntas. O ES2015/ES6 tem duas principais features que se encaixam muito bem, a primeira delas é a class – sim agora você tem classes no JavaScript e elas podem funcionar muito bem com Web Components. O o conceito de class é você conseguir compartilhar blocos de códigos.

Criar um Custom element com ES2015/ES6 é fácil, mas é importante notar que a única subclasses que funciona é de suas próprias classes. Não conseguimos ainda ter subclasses builtins, as questões são sobre alocação de objetos (especialmente para objetos DOM).

Felizmente, nós podemos passar o elemento estendido para o registerElement porque a implementação olha a propriedade protótipo do objeto passado.

Mas para fazermos funcionar precisamos usar transpilers, porque os browsers ainda não estão dando suporte integral ao ES6. Os transpilers mais conhecidos são o Babel e o Google traceur. E também estou usando o Babel na maioria do exemplos.

Então vamos ver um boilerplate básico de custom element usando classes em ES2015/ES6:

class MyElement extends HTMLElement {
  // Quando o elemento é criado
  createdCallback() {};
  // Quando o elemento entra na DOM
  attachedCallback() {};
  // Quando o elemento sai da Dom
  detachedCallback() {};
  // Quando o elemento é adicionado, removido ou atualizado
  attributeChangedCallback(attr, oldVal, newVal) {};
}

document.registerElement('my-element', MyElement);

E se você precisar estender elementos existente, você também pode usando classes em ES2015/ES6:

class DateTime extends HTMLSpanElement {
    createdCallback() {
      this.textContent = "Que dia é hoje: " + new Date().toJSON().slice(0, 10);
    }
  }

  document.registerElement('date-time', {
    prototype: DateTime.prototype,
    extends: span
  });

O código depois de transpilado com Babel, seria assim:

  'use strict';

  var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };

  var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

  var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };

  var DateTime = (function (_HTMLSpanElement) {
    function DateTime() {
      _classCallCheck(this, DateTime);

      if (_HTMLSpanElement != null) {
        _HTMLSpanElement.apply(this, arguments);
      }
    }

    _inherits(DateTime, _HTMLSpanElement);

    _createClass(DateTime, [{
      key: 'createdCallback',
      value: function createdCallback() {
        this.textContent = 'Que dia é hoje: ' + new Date().toJSON().slice(0, 10);
      }
    }]);

    return DateTime;
  })(HTMLSpanElement);

  document.registerElement('date-time', {
    prototype: DateTime.prototype,
    'extends': span
  });

Muito fácil criar Custom Elements com classes ES2015/ES6, não é? Para você ver melhor, tenho um exemplo no Github. Você também pode ver como estender elementos usando ES6 nesse repositório do Zeno Rocha, que está no Github.

Outra feature bem legal além de classes, é ES2015/ES6 Modules – só que ela ainda não é interopéravel com Web Components, pois ambos têm uma abordagem diferente para gerenciamento de dependência. Ainda não sei como eles vão funcionar juntos, mas poder empacotar é um fator chave de Web Components.

Exemplo:

import Dependency from 'my-component';

versus a especificação de HTML Imports.

Web Components é interopéravel não só com ES2015/ES6, mas ele trabalha muito bem com Angular, React, com pré-processadores CSS e com outras bibliotecas também.

Conclusão

Conseguimos fazer com que outras bibliotecas, frameworks e pré processadores trabalhem muito bem juntos.

O que você achou da interopérabilidade de Web Components? Conte suas dúvidas e sugestões nos comentários!

é Front-end Developer tendo atuado em algumas agências. Hoje tem 17 anos e trabalha na maioria do tempo com OpenSource, contribuindo e criando novos projetos. É um dos criadores da primeira weekly dedicada a Web Components e do Web Components the right way.

Ver perfil