Lit e Web Components em projetos reais: experiência após 6 meses de uso
1. Por que escolhemos Lit e Web Components?
A fragmentação de frameworks frontend é um problema real. Em um mesmo ecossistema corporativo, é comum encontrar React em um módulo, Angular em outro e Vue em um terceiro. Cada time defende sua escolha, e o resultado é uma base de código heterogênea, difícil de manter e com componentes duplicados.
A proposta dos Web Components — Custom Elements, Shadow DOM e Slots — sempre foi atraente: criar elementos reutilizáveis independentes de framework. Porém, a API nativa é verbosa e propensa a erros. Foi aí que o Lit entrou.
Lit é uma biblioteca leve (cerca de 5 KB minificada) que abstrai a complexidade dos Web Components, oferecendo reatividade declarativa, templates baseados em tagged literals e um sistema de propriedades reativas. Após 6 meses de uso intenso, posso afirmar que a escolha foi acertada.
Comparando com alternativas:
- Stencil: mais opinativo, gera componentes com bundler próprio, mas adiciona complexidade de build.
- Vanilla Web Components: viável para componentes simples, mas a verbosidade para gerenciar estado e ciclo de vida torna inviável em escala.
- Lit: equilíbrio entre simplicidade e poder, com curva de aprendizado suave para quem já conhece JavaScript moderno.
2. Setup inicial e primeiras impressões
Iniciamos o projeto com Vite + Lit. O boilerplate é mínimo:
npm create vite@latest my-components -- --template lit-ts
O primeiro componente criado foi um botão encapsulado:
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('my-button')
export class MyButton extends LitElement {
static styles = css`
:host {
display: inline-block;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: var(--primary-color, #6200ee);
color: white;
cursor: pointer;
}
button:hover {
opacity: 0.9;
}
`;
@property({ type: String }) label = 'Clique aqui';
@property({ type: Boolean }) disabled = false;
render() {
return html`
<button ?disabled="${this.disabled}">
<slot></slot>
${this.label}
</button>
`;
}
}
A primeira impressão foi surpreendente. O Shadow DOM isola estilos nativamente — sem CSS-in-JS, sem módulos CSS, sem preocupações com vazamento. O desenvolvimento fluiu de forma similar a React, mas sem a necessidade de hooks ou contextos complexos.
3. Arquitetura de componentes no mundo real
Adotamos o design atômico para organizar os componentes:
src/
components/
atoms/ # botão, input, ícone
molecules/ # campo de formulário, card
organisms/ # navbar, tabela, modal
templates/ # layout de página
A comunicação entre componentes foi um ponto de atenção. Inicialmente usamos apenas propriedades reativas (@property), mas rapidamente percebemos a necessidade de eventos customizados para comunicação bidirecional:
// Componente pai escuta evento
<meu-formulario @submit="${this.handleSubmit}">
<meu-input name="email"></meu-input>
</meu-formulario>
// Componente filho dispara evento
this.dispatchEvent(new CustomEvent('submit', {
detail: { email: this.value },
bubbles: true,
composed: true
}));
Para estado global, integramos com RxJS:
import { Subject } from 'rxjs';
const store = new Subject();
export const state$ = store.asObservable();
export function updateState(newState) {
store.next(newState);
}
// No componente
connectedCallback() {
super.connectedCallback();
this.subscription = state$.subscribe(state => {
this.user = state.user;
});
}
disconnectedCallback() {
super.disconnectedCallback();
this.subscription?.unsubscribe();
}
4. Performance e renderização na prática
A reatividade do Lit é eficiente. O decorator @property marca propriedades que disparam re-renderização apenas quando alteradas. O método shouldUpdate permite controle fino:
shouldUpdate(changedProperties) {
return changedProperties.has('visible');
}
O Shadow DOM realmente isola estilos. Em 6 meses, não tivemos um único caso de vazamento CSS entre componentes. A desvantagem é que estilos globais (como variáveis CSS) precisam ser herdados explicitamente via :host ou part.
Benchmarks reais do nosso projeto (uma SPA de dashboard com 40+ componentes):
- Tempo de carregamento inicial: 1.2s (vs. 1.8s com React equivalente)
- First Paint: 0.4s
- Interatividade: 0.9s
A ausência de um virtual DOM pesado faz diferença em dispositivos móveis.
5. Integração com frameworks e sistemas legados
O maior teste foi integrar Web Components dentro de uma aplicação React existente. A solução foi simples:
// React wrapper
function MyButtonWrapper({ label, onClick }) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
el.addEventListener('click', onClick);
return () => el.removeEventListener('click', onClick);
}, [onClick]);
return <my-button ref={ref} label={label}></my-button>;
}
Com Angular e Vue, a integração foi ainda mais natural, pois ambos possuem suporte nativo a Custom Elements.
Um caso real de sucesso: migramos gradualmente um monólito jQuery de 50 mil linhas. Criamos Web Components para substituir partes da interface, e o sistema legado consumia os novos componentes via document.createElement. Sem rewrite, sem quebra de funcionalidades.
6. Manutenção e evolução após 6 meses
O versionamento de componentes foi resolvido com tags semânticas no npm:
@my-org/button@1.2.0
@my-org/table@2.0.0
Testes com Web Test Runner se mostraram robustos:
import { expect, fixture, html } from '@open-wc/testing';
import '../src/my-button.js';
describe('MyButton', () => {
it('renderiza com label padrão', async () => {
const el = await fixture(html`<my-button></my-button>`);
expect(el.shadowRoot.textContent).to.include('Clique aqui');
});
});
Lições aprendidas:
- Funcionou bem: encapsulamento de estilo, reatividade simples, integração com qualquer framework.
- Gerou retrabalho: a falta de um ecossistema de componentes prontos (como Material-UI) nos obrigou a construir tudo do zero. Também enfrentamos dificuldades com acessibilidade em alguns componentes customizados.
7. Comparação final: Lit vale a pena?
Pontos fortes:
- Leveza e performance superiores
- Padronização nativa (Web Components são parte do HTML)
- Independência total de framework — componentes funcionam em qualquer lugar
Pontos fracos:
- Ecossistema menor que React/Vue
- Curva de aprendizado para times acostumados com JSX e hooks
- Ferramentas de debug menos maduras (extensão Chrome para Lit ainda é limitada)
Recomendação baseada em cenários:
- SPAs complexas: Lit é viável, mas faltam bibliotecas de roteamento e estado maduras. Prefira React se o time já tem expertise.
- Micro-frontends: Lit é a escolha ideal. Componentes independentes que funcionam em qualquer shell.
- Design Systems: Lit é excelente. Componentes encapsulados, leves e portáteis.
Após 6 meses, a decisão se mostrou acertada para nosso contexto de design system corporativo com múltiplos times usando frameworks diferentes. A padronização trouxe consistência, a performance agradou e a manutenção se provou sustentável.
Referências
- Documentação oficial do Lit — Guia completo de API, tutoriais e exemplos práticos de Web Components com Lit.
- Web Components na prática: MDN Web Docs — Referência completa sobre Custom Elements, Shadow DOM e Slots.
- Lit vs Stencil: comparação técnica — Análise detalhada das diferenças entre as duas bibliotecas populares de Web Components.
- Integrando Web Components com React — Tutorial prático sobre como usar Custom Elements dentro de aplicações React.
- Testes com Web Test Runner e Lit — Documentação oficial do Web Test Runner, ferramenta de testes para Web Components.
- Design Systems com Web Components — Artigo sobre construção de design systems reutilizáveis utilizando Web Components e Lit.