Se tem um elemento importante no desenvolvimento front-end são os Web Components. Essa suíte de diferentes tecnologias agiliza bastante o processo de desenvolvimento e apoia a organização do código.
Acompanhe neste artigo o que são web components, quando usá-los no seu desenvolvimento -end e de quebra acompanhe um exemplo prático.
Artigo escrito por: Wesley Soares de Souza e Thalita Freire Rodrigues.
Primeiros passos
A muito, muito tempo atrás, quando se falava de desenvolvimento web, principalmente sites institucionais, que geralmente eram estáticos, profissionais de desenvolvimento precisavam conhecer na essência HTML, CSS e um pouco de JavaScript para fazer animações que chamavam a atenção do usuário. A web cresceu, assim como a internet e sua importância no mundo corporativo.
Esse crescimento originou ferramentas que permitiam que a criação de sites fosse feita com facilidade por qualquer pessoa com conhecimentos em computação que estivesse disposta a dedicar algum tempo a essa atividade. Sabemos que existem várias ferramentas online para criação de sites de forma fácil e sem precisar conhecer HTML, CSS ou JavaScript.
O crescimento exponencial da internet fez com que tudo relacionado à web crescesse proporcionalmente, inclusive o trabalho de profissionais de desenvolvimento front end, que se tornaram cada vez mais importantes e complexos devido às necessidades que surgiram como aplicar técnicas de Design Patterns e fazer o reuso de componentes para minimizar a duplicação de código.
A partir dessa situação, surgiram vários frameworks e tecnologias para auxiliar profissionais de desenvolvimento , o que resultou em um leque de escolhas muito grande para ferramentas e tecnologias, além de entender a necessidade do mercado, o que é algo muito volúvel e complexo.
Então, o que fazer? Quando o caminho se torna difícil, precisamos voltar às origens, afinal, independente das tecnologias criadas, o front end se resume em HTML, CSS e JavaScript. Portanto, se você dominar esses elementos em sua essência, a migração entre os frameworks e as tecnologias existentes será muito mais fácil e intuitiva do que imagina.
O que significa “reuso” nesse contexto?
Reuso é a essência para tornar o trabalho de profissionais de desenvolvimento algo mais simples e dinâmico, fazendo-o se preocupar somente com as regras de negócio, o que minimiza o retrabalho.
Não há como citar o reuso de software sem incluir a utilização de componentes de forma lúdica, este encaixe de “peças de lego” forma uma estrutura bem maior, funcional, completa e de fácil manutenção. Essa inclusão de componentes formam os componentes web presentes em frameworks como Angular e React.
Para entender como esses componentes realmente funcionam, iremos criar um modelo utilizando de JavaScript puro, sem a utilização de frameworks ou tecnologias que facilitem o processo.
Conhecimentos necessários para o reuso e criação de componentes em geral
Veja a seguir uma breve introdução sobre os conhecimentos necessários para o reuso e criação de componentes em geral. Você pode usar esses conceitos como base para se aprofundar depois.
Custom Elements
Controlador usado para registrar novos elementos (tags) em HTML que permite herança de elementos pré-existentes como HTMLElement e HTMLParagraph. Além de uma melhor semântica para a página, este controlador também faz com que a customização seja mais eficiente.
Saiba mais em MDN Web Docs e com o artigo de Souders.
Shadow DOM
A sigla DOM (Document Object Model) corresponde ao caminho no qual documentos HTML ou XML são manipulados, conforme demonstrado na figura 01.
Quando há falhas na estrutura HTML como erros de escrita, os navegadores tentam corrigir os erros gerando uma DOM final diferente.
Já a Shadow DOM é uma tecnologia derivada da DOM que permite a criação de componentes que possuem uma estrutura encapsulada que impede que haja conflitos entre CSS da página a qual o elemento se encontra inserido.
Podemos ter um elemento com a classe “img_article” na qual definimos as características por CSS, porém temos um componente inscrito na Shadow DOM com o mesmo nome da classe, mas com outras definições. Ambas funcionarão em contextos distintos, sem que haja conflitos no comportamento esperado de um ou de outro. Você pode conferir mais sobre Shadow DOM com este artigo da Hanashiro e de Bidelman.
Módulos Javascript
Devido à recorrência do JavaScript no cenário atual de desenvolvimento web e a sua aplicação massiva em muitos frameworks de front end, surgiu a necessidade de se utilizar do conceito de modularização para termos scripts mais limpos e podermos criá-los de forma que sejam reutilizáveis. Para utilização dessa estrutura, é necessário que o navegador tenha compatibilidade com as diretivas export e import, as quais somente o navegador Internet Explorer é totalmente incompatível.
Conforme citado, podemos criar um arquivo calculadora.js como no código 01 a seguir:
export function somaDoisValores(a,b) {
return a + b;
}
Código 01 – calculadora.js – Javascript
No arquivo em que a importação da função somaDoisValores() será feita, deve-se declarar em seu escopo o código 02 a seguir. Isso irá permitir a visibilidade do conteúdo que se encontra no arquivo calculadora.js, o que demonstra a opção de reuso em qualquer outro script de qualquer outro projeto.
import { somaDoisValores } from './modules/calculadora.js';
Código 02 – Importação da função somaDoisValores- Javascript
HTML Templates
HTML Templates consistem em trechos de código HTML encapsulados, que podem ser inseridos em documentos através de scripts.
Podemos perceber que, ao carregar a página enquanto o parser processa o conteúdo da tag <template>, esta ação apenas garante que o conteúdo é válido, porém, ele ainda não foi processado completamente. Segue, no código 03 a seguir, a declaração da tag em HTML.
(...)
<body>
<button onclick="showContent()">Mostrar</button>
<template></template>
(...)
Código 03 – Tag template em HTML.
Com a tag template definida e o trecho HTML, podemos manipular o conteúdo através de JavaScript conforme o código 04 a seguir:
<script>
function showContent() {
var temp = document.getElementsByTagName("template")[0];
var clon = temp.content.cloneNode(true);
var title = document.createElement("label");
title.innerHTML = "Nome: "
var input = document.createElement("input");
clon.appendChild(title);
clon.appendChild(input);
console.log(clon)
document.body.appendChild(clon);
}
document.body.appendChild(fragment);
</script>
Código 04 – JavaScript manipulando template.
Na figura 02, podemos ver a criação de um label e um input o qual foi inserido página conforme demonstrado no código 04.
Quer saber mais sobre o assunto? Então leia HTML Living Standard.
Mas o que são web components?
“Web Components é uma suíte de diferentes tecnologias que permite a criação de elementos customizados reutilizáveis — com a funcionalidade separada do resto do seu código — e que podem ser utilizados em suas aplicações web.” Fonte: MSDN Web Docs.
Este conceito permite que componentes sejam criados em JavaScript de forma encapsulada, o que evita conflitos tanto de CSS quanto de JavaScript sem a utilização de frameworks.
A abordagem básica que será utilizada para a implementação é baseada no MSDN Web Docs o qual, sugere que seja seguido os seguintes passos:
- Seguir a sintaxe abordada no ECMASCRIPT 2015 para a criação de um web component.
- Utilizar o método CustomElementRegistry.define() para registrar o novo elemento, que deverá ser incorporado ao Shadow DOM, se necessário.
- Caso haja necessidade utilizar template HTML com <template> ou <slot>
- Por fim, carregar um elemento na página HTML como um componente.
Exemplo prático de web components: Como criar um componente contendo um menu?
Vamos criar um componente chamado my-menu, customizável, o qual poderemos criar na horizontal e vertical, assim como definir seu cumprimento, conforme exibido na figura 03, a seguir.
Para poder ficar bem estruturado como um componente, na figura 04, demonstramos a distribuição do código-fonte do projeto. Para isso, criamos uma pasta chamada compMenu contendo todos os arquivos respectivos ao menu.
Mão na massa!
Caso queira ver o código-fonte que iremos implementar completo, acesse aqui.
Vamos definir a funcionalidade do web component que usaremos de modelo, primeiramente. A ideia é um menu para utilizar em um sistema web, algo bem comum em sistemas e web sites. Como personalização podemos definir seu tamanho (width), sua cor e sua posição através de atributos na tag. Futuramente você poderá inserir outras propriedades conforme verificar a necessidade.
Outra questão seria a estrutura do menu, para ficar algo fácil e funcional iremos incluir um arquivo JSON, com o menu e submenu assim como seus respectivos links.
Para saber mais sobre o conceito de JSON, caso seja algo novo para você, acesse esse tutorial da Oracle.
Menu.html
Este é um arquivo HTML base com uma tag <Nav> adicionada para a criação de um menu, conforme código 05, a seguir. Essa tag é recomendada para componentes deste tipo.
(…)
<nav class="menu">
<ul>
</ul>
</nav>
(…)
Código 05 – menu.html
Menu.json
Este arquivo servirá como estrutura de dados para compor nosso menu de acordo com o código 06, a seguir.
[
{
"name": "Menu 1",
"link": "#",
"submenu":
[
{
"submenu": "sub menu 1",
"sublink": "#"
},
{
"submenu": "sub menu 2",
"sublink": "#"
}
]
}
]
Código 06 – menu.json – Json
MenuComponent.js
Arquivo no qual o web component é desenvolvido. Para que o nosso código fique melhor distribuído, primeira coisa que iremos fazer é criar uma classe chamada myMenu, conforme o código 09 a seguir. Esta classe irá herdar todos as propriedades e métodos da classe HTMLElement.
A classe HTMLElement, fornece acesso ao elemento <html>, que representa a raiz do documento, essa herança será necessário porque iremos utilizar alguns métodos para manipular a raiz do documento. Colocamos no construtor a chamada ao método super() para que possamos ter acesso aos métodos da classe pai HTMLElement, com os quais iremos manipular nosso documento.
Após a criação da classe, chamamos o método define() do objeto customElements, na linha 06 do código, para a criação do nosso componente. O objeto customElement, permite registrar um novo elemento na página, a sintaxe empregada segue o padrão definido pelo ES 2015.
O método define(), presente na classe customElement é quem realiza a criação do web component, ele possui dois parâmetros, no primeiro irá definir o nome do seu componente e no segundo invocar o construtor da classe passada como parâmetro.
class myMenu extends HTMLElement {
constructor() {
super();
}
}
customElements.define('my-menu', myMenu);
Código 07 – criação da classe myMenu – Javascript
Implementando na shadow DOM
Agora vamos criar um Shadow DOM através do método attachShadow(), conforme a linha 04 definida no código 08, a seguir. O método attachShadow é responsável por vincular uma árvore DOM fantasma ao elemento raiz da página nesse caso. Será dentro desse Shadow DOM criado que o nosso componente irá ser renderizado e será encapsulado, totalmente apartado a qualquer outro elemento na DOM da página principal que chamou a tag, evitando conflitos de CSS ou JavaScript.
(…) constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const position = this.getAttribute('position');
const size = this.getAttribute('size');
(…)
Código 8 – Atualização no construtor da classe MyMenu – Javascript
Foi criado também duas constantes, position e size que serão atributos informados na tag my-menu da página que irá utilizar o componente. Para tanto utilizamos o método getAttibute() que retorna um argumento específico que se encontra declarada na tag html, conforme declarado no código 09 a seguir. O atributo position irá definir se o menu será renderizado na vertical ou horizontal. O atributo size definirá a largura do componente. Veremos mais à frente na prática como ficaria.
Com o intuito do código ficar mais legível e organizado iremos criar três métodos:
- setDetailMenu(position, size, shadow): Esse método tem como função renderizar a folha de estilo em CSS que será aplicada no Shadow DOM criado. Conforme descrito no Código 09 a seguir, o nosso método é dividido por uma validação que irá verificar se a posição é horizontal ou vertical. Em cada situação é feita a inserção do respectivo CSS e, na linha 19, inserimos o atributo size na propriedade width do CSS para determinar a largura do menu renderizado.
function setDetailMenu(position, size, shadow){
const VStyle = document.createElement('style');
if (position === 'horizontal'){
VStyle.textContent =`
.menu li{
display: inline;
float: left;
}
.menu li ul{
position:absolute;
top:40px;
background-color:DarkGray;
display:none;
}
.menu li:hover ul, .menu li.over ul{
display:block;
}
`;
} else {
VStyle.textContent =`
.menu{
width: ${size};
}
.menu li ul{
position:absolute;
margin-top: -40px;
margin-left:${size};
background-color:DarkGray;
display:none;
}
.menu li:hover ul, .menu li.over ul{
display:block;
}
`
}
VStyle.append(`
.menu ul{
margin: 16px 0;
padding: 0;
background-color: gray;
overflow: hidden;
}
.menu li a {
display: block;
color: white;
padding: 8px 16px;
text-decoration: none;
}
.menu li a:hover{
background-color: black;
}
.menu li.active{
background-color: black;
}
`);
shadow.appendChild(VStyle);
}
Código 09 – setDetailMenu() – Javascript
Por fim, é realizado o método appendChild() no Shadow DOM passada como parâmetro a constante VStyle, que possui a estrutura CSS definida para a renderização do menu e seu funcionamento.
- getMyComponent(componente, shadow): Esse método tem como finalidade renderizar o HTML base de nosso componente presente no arquivo apresentado anteriormente, menu.html. Conforme o código 10, a seguir, definimos o retorno com método fetch(), nela passamos o endereço em que se encontra o HTML e a configuração do header para que possamos manipular o retorno como um texto do tipo HTML, na linha 11 através do método appendChild() adicionamos a página ao Shadow DOM caso o retorno seja válido na requisição.
function getMyComponent(component, shadow){
const myHeaders = new Headers();
myHeaders.set('Content-Type', 'text/html;charset=windows-1251');
const config = { headers: myHeaders };
return fetch(component, config)
.then(res => res.text())
.then((response) => {
var cont = document.createElement('div');
cont.innerHTML = response;
shadow.appendChild(cont);
console.log(response);
}).catch((error) => {
console.log('ocorreu um erro na operação: ' + error.message);
})
}
Código 10 – método getMyComponent() – Javascript
- getMyMenuOptions(componente, shadow): Esse método tem como função carregar o menu.json definido como a estrutura de menus e submenus, conforme tratado anteriormente. O código 11 a seguir, na linha 7, é pesquisado em nosso Shadow DOM uma tag <ul>, presente na estrutura HTML e carregado na variável menu, onde a estrutura HTML do menu será montada. Na linha 8, após ser carregado o arquivo, sem erros, ele é como retorno do método. A estrutura, conforme linha 09 em diante, é montada em uma lista com as tags <ul> e <li> com os atributos presentes no arquivo JSON montando os itens e subitens do menu.
function getMyMenuOptions(json, shadow){
let menu;
fetch(json)
.then((resp) => resp.json())
.then(function(response) {
let resMenu = response;
let menu = shadow.querySelector('ul');
return resMenu.map(function(newMenu) {
let item = document.createElement('li');
item.innerHTML = '<a href='${newMenu.link}'>${newMenu.name}</a>`;
menu.appendChild(item);
if (newMenu.submenu.length > 0){
let subItem = document.createElement('ul');
newMenu.submenu.forEach(sub => {
let sitem = document.createElement('li');
sitem.innerHTML = `<a href='${sub.sublink}'>${sub.submenu}</a>`;
subItem.appendChild(sitem);
})
item.appendChild(subItem);
}
})
}).catch((error) => {
console.log('ocorreu um erro na operação: ' + error.message);
});
}
Código 11 – método getMyMenuOptions() – Javascript
Finalizando o método Construtor
Ao chamar os métodos criados conforme as linhas 5,6 e 7 do código 12 a seguir, finalizamos a criação de nosso web component. É possível visualizar o endereço da URL que foi passado como parâmetro nas linhas 6 e 7, lembrando que criamos um servidor com o http-server que gerou o endereço passado por parâmetro, conforme discutido anteriormente. Nos três métodos, passamos a Shadow DOM criada para que os itens que compõem nosso web component sejam inseridos.
(…)
const shadow = this.attachShadow({ mode: 'open' });
const position = this.getAttribute('position');
const size = this.getAttribute('size');
setDetailMenu(position, size, shadow);
getMyComponent('http://localhost:8080/compMenu/menu.html', shadow);
getMyMenuOptions('http://localhost:8080/compMenu/menu.json', shadow)
}
Código 12 – Versão final do constructor() da classe MyMenu – Javascript
Utilizando o componente criado em nossa index.html
Para utilizarmos o nosso componente, primeiramente declaramos o script que contém a classe myMenu, conforme demonstrado no código 13 a seguir. Acrescentamos no head de nossa página a declaração à qual definimos a propriedade type como module e também chamamos a propriedade defer, que faz com que o script somente seja carregado após a página HTML terminar de subir na DOM.
(…)<script type="module" src="compMenu/menuComponent.js" defer></script>(…)
Código 13 – Declaração do módulo do web component – html
Dentro do HTML, conforme código 14, a seguir, chamamos a tag <my-menu> e passamos o atributo position e size com tamanhos pré-determinados.
<body>
<div class="container">
<my-menu
position="horizontal"
size="100%"
></my-menu>
</div>
</body>
Código 14 – arquivo index.html
O resultado podemos visualizar na figura 05, a seguir:
Conclusão
A separação do código em componentes distintos e encapsulados é um conceito que faz parte da Orientação a Objeto de forma aplicada. Isso evita que ocorram conflitos no front-end quando se tornam maiores e mais complexos, sem falar na ajuda que traz para se realizar manutenção em apenas um lugar do código.
As primeiras versões do Angular trabalhavam com Shadow DOM, que posteriormente foi migrado para o Virtual DOM, mas isso é história para outro artigo. Vemos que a utilização do Shadow DOM traz uma forma bem legal e dinâmica para se trabalhar.
Sugerimos fazer outras estruturas, melhorar o menu, conforme citado o código se encontra disponível no Github.
Referências
- Quer criar o próprio site? Veja 10 serviços que podem dar uma mãozinha.
- 4 Design Patterns you should know for web Development: Observer, Singleton, Strategy and Decorator
- 6 frameworks front-end mais utilizados no mercado de tecnologia
- Chega de frameworks! Vamos ao natural com Web Components!
- Usando custom elements
- Desempenho e elementos personalizados
- O que é DOM, Virtual DOM e Shadow DOM?
- O que é DOM, Virtual DOM e Shadow DOM?
- Shadow DOM v1: Self-Contained Web Components
- Módulos JavaScript
- HTML Living Standard
- Web Components
- O que é CORS e como resolver os principais erros
- http-server servidor leve e rápido
- Código fonte do projeto do artigo no github
- JSON
- HTMLElement
- fetch()
- HTML <script> defer Attribute
- Usando Fetch