Executando verificação de segurança...
15

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.

Carregando publicação patrocinada...
3

Primeiro, parabéns pelo post e solução! Essa dor que você descreveu é muito real.

É um problema enorme no desenvolvimento web moderno. O caos recente no ecossistema npm está jogando uma luz sobre isso, e as coisas estão começando a mudar. Toda proposta de solução é mais que bem-vinda.

Mas, honestamente, não compro essa ideia.

Em vez de criar mais uma camada de abstração que, no fundo, só facilita cometer o erro. Existe uma solução muito mais simples: abolir as dependências externas!!!

Se você realmente precisa de uma funcionalidade, encontre um código com uma licença permissiva (BSD/MIT), copie para dentro do seu repositório. Pronto. Agora é seu código!

A responsabilidade é sua.

Penso que essa uma abordagem é mais sólida, embora, sem dúvida, exija mais disciplina e esforço. Mas é assim que se constrói software verdadeiramente robusto.

Um abraço e bons estudos!

2

Olhando por cima parece uma solução alternativa mas na verdade é apenas complementar, o problema que o post resolve não é simplesmente uma biblioteca externa mas a quantidade de pontos onde é necessário mudanças se a biblioteca for alterada, vamos supor que se use a sua solução, biblioteca built-in, mas e se precisar, por qualquer motivo que seja, mudar a biblioteca? Como fica?

2

Excelente ponto. Sua análise está absolutamente correta.

Independentemente de estar usando uma biblioteca de terceiros ou outro "módulo" do software, a regra de ouro sempre foi comunique-se através de uma interface estável.

Mas o "x da questão" é:

comecei a reparar em algo que tem me incomodado: a dependência excessiva de libs externas.

Sua pergunta, "Como fica se precisar mudar a biblioteca?"

A mudança se torna uma tarefa de engenharia, e não um ato de desespero. Você tem o luxo de planejar a substituição, talvez até manter as duas versões coexistindo durante um período de transição, e finalmente realizar um "decommissioning" da versão antiga.

O oposto disso é ter que reescrever centenas de chamadas às pressas porque o projeto quebrou em produção depois que dependência falhou.

2

Muitíssimo obrigado pelo feedback, pessoal! Estou realmente feliz com essa discussão.

O ponto central que percebi é o uso excessivo de bibliotecas externas espalhadas pelo projeto. Não tenho nada contra libs em si, mas quando elas estão profundamente entranhadas no código, qualquer alteração se torna complicada e arriscada.

Entendo o argumento de trazer o código externo para dentro do projeto, assumindo a responsabilidade de mantê-lo. Mas existem alguns contras:

No contexto atual, não usar libs externas muitas vezes equivale a improdutividade. Prazos apertados, times desalinhados e sem comando central tornam o trabalho caótico.

Muitas libs são enormes; você quer apenas a funcionalidade, não ler e manter toda a base de código. Vulnerabilidades surgem, atualizações são constantes — manter tudo manualmente dentro do projeto é um retrabalho enorme.

Mesmo trazendo o código para dentro, manter a lib isolada facilita atualizar e substituir versões, sem quebrar padrões de uso, props ou instâncias.

Dito isso, sou super a favor de código próprio. Quase todos os componentes que uso eu crio do zero: entendo, controlo e distribuo como preciso. É muito mais produtivo no meu contexto.

Mas em alguns casos, não aproveitar recursos externos é contraproducente. Um bom exemplo é o SDK do Jitsi para videoconferências: reinventar algo tão robusto seria perda de tempo, mesmo que fosse implementada uma camada de domínio própria para centralizar o uso.

Então, minha visão é que libs externas são válidas, desde que bem abstraídas e gerenciáveis, para não tornar qualquer alteração ou atualização uma emergência.

No fim das contas, gosto de lembrar de uma frase que resume bem:

Se você quer realmente fazer uma torta de maçã do zero, terá que recriar o universo primeiro.

1

Sim, meu comentario obviamente foi uma provocação, não existe bala de prata! Mas aqui vai mais uma então:

Muitas libs são enormes; você quer apenas a funcionalidade

Isso normalmente é um enorme indicativo que voce não precisa da lib em qeustão ;)

Use e abuse de SDKs oficiais e da biblioteca padrão do seu runtime, mas todo cuidado é pouco com codigo "desconhecido".

1
1
1

algo que tem me incomodado: a dependência excessiva de libs externas

Quanto tempo seu time demoraria

No meu tempo livre e no meu tempo trabalhando eu também me sinto incomodado com a dependência excessiva de libs externas

1

Também me incomoda demais 😅.
No meu dia a dia, tento equilibrar: usar libs quando realmente agregam, mas sempre que possível abstraindo ou criando camadas próprias, para que o projeto continue previsível e fácil de manter… embora quase nunca seja fácil, rsrs.

Tantos passos que, se fossem tomados antes de iniciar o projeto, economizariam muita dor de cabeça. Mas, na prática, vários fatores fazem com que isso quase nunca aconteça antes do pontapé inicial.