O seu projeto é viciado? 💉💻
Depois de acompanhar projetos em diferentes stacks — PHP, React, NestJS, etc. — comecei a reparar em algo que tem me incomodado: a dependência excessiva de libs externas.
Não me entenda mal: acho incrível termos à mão ferramentas como shadcn/ui, Material UI, Day.js, Prisma, Iconify, Font Awesome e tantas outras que aceleram o desenvolvimento. Mas a questão é:
👉 Até que ponto essas libs estão entrando direto no core do projeto, sem nenhuma camada de abstração?
Já parou pra pensar no que faria se, por algum motivo, a lib de ícones que você usa deixasse de existir amanhã? Quanto tempo seu time demoraria para refatorar todas as chamadas espalhadas pelo código?
Nada é eterno, muito menos na tecnologia. Recursos desaparecem, deixam de ser atualizados ou se tornam inviáveis para a mantenedora. E mesmo sabendo disso, continuamos criando sistemas onde parece ter mais código externo do que código próprio.
Recentemente, presenciei um projeto novo em que, na mesma view, havia um input do Shadcn e outro do Material UI. E os ícones… bom, esses parecem ser sempre um ponto crítico. Tudo isso em um projeto que tinha um Figma estruturado, mas que na hora da implementação acabava sendo ignorado em favor de soluções prontas, sem um padrão claro.
💡 A Solução: Abstraindo Ícones na Prática
Para resolver essa questão — começando pelos ícones —, criei uma abordagem de uso controlado, centralizado e abstrato. O objetivo é simples: o nosso código de aplicação nunca deve saber qual biblioteca de ícones está sendo usada.
A estratégia se baseia em duas peças principais:
1 - Um componente "Wrapper" (ou "casca") que será o único ponto de contato para exibir ícones no projeto.
2 - Um mapa de referências centralizado que traduz um nome amigável (ex: calendar_edit) para o ícone real da biblioteca.
Vamos ver como isso funciona no código.
1. O Componente Wrapper: Icon/index.tsx
Primeiro, criamos nosso próprio componente <Icon />. Ele recebe um name (um nome que nós definimos) e passa todas as outras propriedades para o componente da biblioteca externa, nesse caso, o <Icon /> do @iconify/react.
// src/components/Icon/index.tsx
import { Icon as IconifyIcon } from '@iconify/react';
import type { ComponentProps } from 'react';
import { IconsReference, type IconKey } from './icon-reference';
interface IconProps extends Omit<ComponentProps<typeof IconifyIcon>, 'icon'> {
name: IconKey;
size?: number;
}
export function Icon({ name, size = 16, ...props }: IconProps) {
const iconData = IconsReference[name];
return (
<IconifyIcon
icon={iconData.value}
width={size}
height={size}
{...props}
/>
);
}
2. O Mapa de Referências: Icon/icon-reference.ts
Aqui está a mágica. Criamos um objeto que funciona como nosso "dicionário" de ícones. É neste arquivo, e apenas aqui, que fazemos a ligação com a biblioteca externa.
// src/components/Icon/icon-reference.ts
import type { IconifyIcon } from "@iconify/react/dist/iconify.js";
type IconData = { type: 'data'; value: IconifyIcon };
type IconString = { type: 'string'; value: string };
export const IconsReference = {
angle_down_line: { type: 'string', value: 'fa:angle-down' },
calendar_regular: { type: 'string', value: 'majesticons:calendar' },
calendar_day_regular: { type: 'string', value: 'ic:round-today' },
calendar_edit_regular: { type: 'string', value: 'ic:round-edit-calendar' },
check_fill: { type: 'string', value: 'fe:check' },
} as const satisfies Record<string, IconData | IconString>;
// Extrai a tipagem das chaves para garantir o autocomplete no código!
export type IconKey = keyof typeof IconsReference;
Com essa estrutura, ganhamos superpoderes:
Manutenção à Prova de Futuro: Se amanhã decidirmos abandonar o Iconify e usar o React-Icons ou FontAwesome, só precisamos modificar dois arquivos: Icon/index.tsx para chamar o novo componente e Icon/icon-reference.ts para atualizar os valores. Nenhum outro lugar do código precisa ser tocado.
Consistência e Padrão: O IconsReference se torna a única fonte da verdade. Acabou a caça por nomes de ícones na documentação da lib. O time só pode usar os ícones que foram previamente definidos, garantindo consistência com o Design System (Figma).
Developer Experience (DX): Graças ao TypeScript (type IconKey), ao usar nosso componente em qualquer lugar do projeto, teremos autocomplete com todos os nomes de ícones disponíveis.
Flexibilidade Total: Note que a estrutura { type, value } é extremamente poderosa. Podemos usar ícones de string do Iconify (type: 'string') ou até mesmo passar os dados de um SVG customizado (type: 'data'). Isso permite unificar ícones de múltiplas fontes sob uma única API.
Essa abordagem de criar uma "camada anticorrupção" não é um devaneio, mas sim um princípio de design de software robusto. E o melhor: ela se aplica a muito mais do que apenas ícones.
O que achou da solução? Podemos aplicar a mesma lógica para componentes de UI, serviços de data, chamadas de API e muito mais.
E pessoal agora sendo bem informal, talvez seja um problema que ninguém mais tenha passado, mas como passei pelos mesmos problemas em uns 10 projetos da empresa, tendo que resolver com passos simples como os da demonstração, fiquei verdadeiramente empolgo em compartilhar essa com vocês, se tiverem uma forma melhor ainda de implementar, manda aí que ficarei feliz em receber um feedback de vocês, no mais, espero ter contruído com essa primeira publicação minha para essa maravilha de pedaço de internet.