Português (Portugal)
  • parcel
  • vite
  • js
  • esbuild
  • bundler
  • monorepo

De Parcel para Vite: A história curta de uma migração de 100 mil linhas de código

Migrámos os nossos três projetos de frontend de Parcel para Vite, e o processo foi... suave.

Gao
Gao
Founder

A história de fundo

Temos três projetos principais de frontend na Logto: a experiência de login, a consola e a visualização ao vivo. Estes projetos estão todos em TypeScript, React, e módulos SASS; no total, têm cerca de 100 mil linhas de código.

Adorávamos o Parcel pela sua simplicidade e configuração zero. Ainda me lembro do dia em que fiquei chocado com a facilidade de configurar um novo projeto com o Parcel. Basta executar parcel index.html e boom, todas as dependências necessárias são instaladas e o projeto está a funcionar. Se fores um desenvolvedor "experiente", podes sentir o mesmo quando comparas com os velhos tempos de configuração de projetos usando Gulp e Webpack. O Parcel é como uma varinha mágica.

A simplicidade do Parcel foi a principal razão pela qual ficámos com ele tanto tempo, mesmo que às vezes fosse um pouco temperamental. Por exemplo:

  • O Parcel às vezes não conseguia agrupar o projeto porque não conseguia encontrar alguns ficheiros de chunk que na verdade estavam lá.
  • Precisava de algumas configurações hacky para funcionar com a nossa configuração de monorepo.
  • Não suporta o MDX 3 nativamente, por isso tivemos que criar um transformador personalizado para ele.
  • Não suporta chunks manuais (no momento da escrita, o recurso de chunks manuais ainda está em fase experimental), o que é aceitável na maioria das circunstâncias, mas às vezes é necessário.

Então, por que decidimos migrar para outra coisa?

  1. Estávamos presos no Parcel 2.9.3, que foi lançado em junho de 2023. Sempre que uma nova versão era lançada depois disso, tentávamos atualizar, mas sempre falhava com erros de compilação.
  2. A versão mais recente do Parcel era a 2.12.0, lançada em fevereiro de 2024. Embora tenha commits quase diários, nenhuma nova versão foi lançada desde então.

Alguém até abriu uma discussão para perguntar se o Parcel tinha morrido. A resposta oficial é que não, o Parcel ainda está vivo, mas está num estado de estamos-a-trabalhar-numa-grande-reforma-e-não-temos-tempo-para-lançamentos-menores. Para nós, é como uma "morte de pato": A versão mais recente que podemos usar é de mais de um ano atrás, e não sabemos quando a próxima versão será lançada. Parece que está morto, age como se estivesse morto, então está morto para nós.

Pedidos de pull de atualização do Parcel

Confia em mim, tentámos.

Por que Vite?

Conhecíamos o Vite através do Vitest. Há alguns meses, estávamos cansados do suporte ESM do Jest (em testes) e queríamos experimentar algo novo. O Vitest conquistou os nossos corações com o suporte nativo a ESM e a compatibilidade com Jest. Tem uma experiência de desenvolvedor incrível, e é alimentado por Vite.

O status quo

Podes ter configurações diferentes no teu projeto, mas geralmente encontrarás substituições de plugins à medida que o ecossistema do Vite floresce. Aqui estão as nossas configurações no momento da migração:

  • Monorepo: Usamos workspaces do PNPM (v9) para gerir o nosso monorepo.
  • Módulo: Usamos módulos ESM para todos os nossos projetos.
  • TypeScript: Usamos TypeScript (v5.5.3) para todos os nossos projetos com aliases de caminho.
  • React: Usamos React (v18.3.1) para todos os nossos projetos de frontend.
  • Estilos: Usamos módulos SASS para estilos.
  • SVG: Usamos SVG como componentes React.
  • MDX: Temos MDX com Markdown Estilizado GitHub e suporte a Mermaid.
  • Carregamento preguiçoso: Precisamos carregar preguiçosamente algumas das nossas páginas e componentes.
  • Compressão: Produzimos ativos comprimidos (gzip e brotli) para os nossos builds de produção.

A migração

Começámos a migração criando um novo projeto Vite e brincando com ele para ver como funciona. O processo foi suave e a migração real levou apenas alguns dias.

Suporte pronto para uso

O Vite tem suporte pronto para uso para monorepo, ESM, TypeScript, React e SASS. Só precisámos instalar os plugins e configurações necessários para fazer funcionar.

Alias de caminho

O Vite tem suporte embutido para aliases de caminho, por exemplo, no nosso tsconfig.json:

Só precisámos adicionar a mesma resolução no nosso vite.config.ts:

Nota que o caminho de substituição deve ser um caminho absoluto, enquanto é relativo à raiz do projeto. Alternativamente, podes usar o plugin vite-tsconfig-paths para ler os aliases de caminho do tsconfig.json.

React Fast Refresh e HMR

Embora o Vite tenha suporte embutido para HMR, é necessário instalar um plugin para ativar o React Fast Refresh. Usámos o plugin @vitejs/plugin-react que é fornecido pela equipa do Vite e tem ótimo suporte para funcionalidades do React como o Fast Refresh:

SVG como componente React

Usamos o plugin vite-plugin-svgr para converter SVGs em componentes React. É tão simples como adicionar o plugin à configuração do Vite:

No entanto, não especificámos em que condição os SVGs devem ser convertidos em componentes React, por isso todas as importações foram convertidas. O plugin oferece uma melhor configuração padrão: apenas converter os SVG que são importados com a extensão .svg?react. Atualizámos as nossas importações em conformidade.

Módulos SASS

Embora o Vite tenha suporte embutido para módulos SASS, há algo de que precisamos cuidar: como os nomes das classes são formatados. Pode ser problemático para os utilizadores e para os nossos testes de integração se os nomes das classes não forem formatados de forma consistente. A configuração de uma linha no vite.config.ts pode resolver o problema:

Aliás, o Parcel e o Vite têm sabores diferentes para importar ficheiros SASS:

A sintaxe * as, no entanto, funciona no Vite, mas causará a perda de nomes de classes modularizadas quando usares chaves dinâmicas para aceder ao objeto styles. Por exemplo:

Suporte ao MDX

Como o Vite utiliza o Rollup sob o capô, podemos usar o plugin oficial @mdx-js/rollup para suportar o MDX, bem como os seus plugins. A configuração parece-se com isto:

O plugin remarkGfm é usado para suportar o Markdown Estilizado GitHub, e o plugin rehypeMdxCodeProps é usado para passar as props para os blocos de código nos ficheiros MDX, como faz o Docusaurus.

Suporte ao Mermaid dentro do MDX

Gostaríamos de usar diagramas Mermaid nos nossos ficheiros MDX como outras linguagens de programação. O uso deve ser tão simples quanto outros blocos de código:

Deve ser renderizado como:

Como a nossa aplicação suporta temas claro e escuro, codificámos um pouco para fazer os diagramas Mermaid funcionarem com o tema escuro. Um componente React foi criado:

useTheme é um hook personalizado para obter o tema atual do contexto. A biblioteca mermaid é importada de forma assíncrona para reduzir o tamanho de carregamento inicial da página.

Para o bloco de código no ficheiro MDX, temos um componente unificado para fazer o trabalho:

Finalmente definimos o MDX provider da seguinte forma:

Carregamento preguiçoso

Isto não é uma coisa específica do Vite, ainda vale a pena mencionar já que atualizámos as nossas páginas para usar o carregamento preguiçoso durante a migração, e nada quebrou depois.

O React tem uma função embutida React.lazy para carregar componentes preguiçosamente. No entanto, pode causar alguns problemas quando estás a iterar rapidamente. Criámos uma pequena biblioteca chamada react-safe-lazy para resolver os problemas. É um substituto direto para o React.lazy e uma explicação detalhada pode ser encontrada neste post de blog.

Compressão

Há um plugin prático chamado vite-plugin-compression para produzir ativos comprimidos. Suporta compressão gzip e brotli. A configuração é simples:

Chunks manuais

Uma grande funcionalidade do Vite (ou do Rollup subjacente) são os chunks manuais. Embora React.lazy seja usado para carregar componentes preguiçosamente, podemos ter mais controlo sobre os chunks especificando os chunks manuais para decidir quais componentes ou módulos devem ser agrupados juntos.

Por exemplo, podemos primeiro usar o vite-bundle-visualizer para analisar o tamanho do bundle e as dependências. Depois podemos escrever uma função adequada para dividir os chunks:

Servidor de desenvolvimento

Ao contrário do build de produção, o Vite NÃO agrupará o teu código fonte no modo de desenvolvimento (incluindo dependências ligadas no mesmo monorepo) e tratará cada módulo como um ficheiro. Para nós, o navegador carregará centenas de módulos pela primeira vez, o que pode parecer loucura, mas na verdade está tudo bem na maioria dos casos. Podes ver a discussão aqui.

Se isto for um problema para ti, uma solução alternativa mas não perfeita é listar as dependências ligadas na opção optimizeDeps do vite.config.ts:

Isto fará o "pré-agrupamento" das dependências ligadas e tornará o servidor de desenvolvimento mais rápido. A armadilha é que o HMR pode não funcionar como esperado para as dependências ligadas.

Além disso, usamos um proxy que serve os ficheiros estáticos em produção e proxy as solicitações para o servidor Vitest em desenvolvimento. Temos algumas portas específicas configuradas para evitar conflitos, e também é fácil configurar no vite.config.ts:

Variáveis de ambiente

Ao contrário do Parcel, o Vite usa uma abordagem moderna para lidar com variáveis de ambiente usando import.meta.env. Ele carregará automaticamente os ficheiros .env e substituirá as variáveis no código. No entanto, exige que todas as variáveis de ambiente comecem com VITE_ (configurável).

Enquanto usávamos o Parcel, ele simplesmente substituía as variáveis process.env sem verificar o prefixo. Então criámos uma solução alternativa usando o campo define para tornar a migração mais fácil:

Isto permite-nos adicionar gradualmente o prefixo às variáveis de ambiente e remover o campo define.

Conclusão

É isso! Migrámos com sucesso os nossos três projetos de frontend de Parcel para Vite, e espero que esta curta história possa ajudar na tua migração. Aqui está como a configuração ficou no final: