[PITCH] el.js - sua nova biblioteca de elementos
O problema...
Até alguns dias atrás, eu escrevia HTML literals para adicionar elementos no meu HTML a partir de dados que vinha da API. Eu nunca fui de usar react, no máximo um lit-html, para criar componentes. Sempre fui mais do vanilla javascript. Todas as soluções que usava tinham que compatíveis com o mínimo de tranqueira possível.
Era o caso de sempre ter que fazer:
const text = "Paris é a capital da França.";
const foo = /*html*/`
<div class="card">
<div class="card-header">Paris</div>
<div class="card-body">
${escapeHTML(text)}
</div>
</div>`;
document.body.append(foo);
E caia sempre no mesmo problema: era uma gambiarra horrível. Essa prática me deixava sujeito à:
- legibilidade: tornava o código ruim, ilegível, dependia de extensões para fazer highlight do que tava naquele template literal.
- performance: a estrutura daquele código era uma string. Quando fazia o
Node.append(string)
, aquilo era convertido/interpretado para HTML e então criava um HTMLElement para adicionar naqueleNode
. Se eu fosse fazer isso em um loop, o consumo de processamento era bem alto. - interpolação e segurança: se eu precisasse criar um conteúdo, precisaria ter certeza que aquele conteúdo não ia quebrar todo o HTML naquele template literal. Para isso, tinha que recorrer à criar métodos que "sanitizavam" a entrada para não enfiar um ataque XCSS ali naquele template literal.
Todas essas camadas de complexidade e vulnerabilidade eram expostas ao usar template literals. Se eu quisesse fazer "do jeito certo", teria que usar document.createElement
, e o código acima seria o equivalente à:
const card = document.createElement('div');
card.className = 'card';
const cardHeader = document.createElement('div');
cardHeader.className = 'card-header';
cardHeader.textContent = 'Paris';
const cardBody = document.createElement('div');
cardBody.className = 'card-body';
cardBody.innerText = text;
card.appendChild(cardHeader);
card.appendChild(cardBody);
return card;
E sinceramente, não gosto muito de OOP. Achei essa solução horrível para quando tinha que criar um componente com mais uns 10 componentes aninhados. Todos com classes, atributos, event-handlers...
E foi aí que fiz a
A solução!
Eu criei uma função, chamada el()
. O que ela faz? Sabe todo o código do problema acima? Eu consigo reescrever com:
const foo =
el(".card",
el(".card-header", "Paris"),
el(".card-body", text));
document.body.appendChild(foo); // foo é um HTMLElement!
E está aí a mágica dessa super biblioteca. É um wrapper funcional para o document.createElement
.
Ela automaticamente entende que cada argumento da função el
após o primeiro é um "encaixe" para o elemento que está sendo criado. Posso usar apenas com o emmet embutido:
const countries = ['Brazil', 'Russia', 'India'];
el("select#country",
...countries.map(c => el("option", c)));
/*
<select id="country">
<option>Brazil</option>
<option>Russia</option>
<option>India</option>
</select>
*/
Ou criar componentes:
el.defineComponent('card', attr =>
el("div.card",
el("div.card-header", attr.title),
el("div.card-body", ...attr.slot)));
E usar como:
el("card", { title: "Paris" }, "Paris é a capital da França!");
/*
<div class="card" title="Paris">
<div class="card-header">Paris</div>
<div class="card-body">Paris é a capital da França!</div>
</div>
*/
E então?
Então, eu pude usar o el()
para resolver os três problemas mencionados anteriormente, com apenas 4.1 KB!. A legibilidade não fica ruim, e na documentação até deixei um exemplo de como fazer elementos stateful/reactive.
Seu uso foi feito para ser usado em qualquer lugar que tenha um document.createElement
.
Estou mostrando para vocês essa engenhoca. Fiquem a vontade para comentarem o que pensam disso.