De Parcel a Vite: Uma curta história de uma migração de 100K LOC
Migramos nossos três projetos frontend de Parcel para Vite, e o processo foi... tranquilo.
A história por trás
Temos três principais projetos frontend no Logto: a experiência de login, o Console e a pré-visualização ao vivo. Esses projetos são todos em TypeScript, React, e módulos SASS; no total, eles têm cerca de 100K linhas de código.
Amávamos o Parcel por sua simplicidade e configuração zero. Eu ainda posso lembrar do dia em que fiquei chocado com quão fácil era configurar um novo projeto com o Parcel. Você pode simplesmente rodar parcel index.html
e boom, todas as dependências necessárias são instaladas e o projeto está rodando. Se você é um desenvolvedor "experiente", pode sentir o mesmo ao comparar com os velhos tempos de configuração com Gulp e Webpack. Parcel é como uma varinha mágica.
A simplicidade do Parcel foi a principal razão pela qual ficamos com ele por tanto tempo, mesmo que ele pudesse ser temperamental às vezes. Por exemplo:
- Parcel às vezes falhava ao empacotar o projeto porque não conseguia encontrar alguns arquivos de chunk que na verdade estavam lá.
- Precisava de algumas configurações improvisadas para funcionar com nossa configuração de monorepo.
- Não suportava MDX 3 nativamente, então tivemos que criar um transformador personalizado para ele.
- Não suporta chunks manuais (no momento da escrita, a funcionalidade de chunks manuais ainda está em estágio experimental), o que é aceitável na maioria das circunstâncias, mas às vezes você precisa disso.
Então por que decidimos migrar para outra coisa?
- Ficamos presos na versão 2.9.3 do Parcel, lançada em junho de 2023. Toda vez que uma nova versão era lançada depois disso, tentávamos atualizar, mas sempre falhava com erros de build.
- A versão mais recente do Parcel era 2.12.0, lançada em fevereiro de 2024. Embora haja commits quase diários, nenhuma nova versão foi lançada desde então.
Alguém até abriu uma discussão perguntando se o Parcel está morto. A resposta oficial é que não, o Parcel ainda está vivo, mas está em um estado de "estamos trabalhando em uma grande refatoração 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.
Por que Vite?
Conhecíamos o Vite por meio do Vitest. Alguns meses atrás, estávamos cansados do suporte ESM do Jest (em testes) e queríamos experimentar algo novo. Vitest conquistou nossos corações com o suporte nativo a ESM e a compatibilidade com Jest. Ele tem uma experiência de desenvolvedor incrível, e é alimentado pelo Vite.
O status quo
Você pode ter configurações diferentes em seu projeto, mas geralmente encontrará substituições de plugins, já que o ecossistema do Vite está florescendo. Aqui estão nossas configurações no momento da migração:
- Monorepo: Usamos workspaces do PNPM (v9) para gerenciar nosso monorepo.
- Módulo: Usamos módulos ESM em todos os nossos projetos.
- TypeScript: Usamos TypeScript (v5.5.3) em todos os nossos projetos com aliases de caminho.
- React: Usamos React (v18.3.1) em todos os nossos projetos frontend.
- Estilização: Usamos módulos SASS para estilização.
- SVG: Usamos SVGs como componentes React.
- MDX: Temos MDX com suporte ao GitHub Flavored Markdown e Mermaid.
- Carregamento sob demanda: Precisamos carregar sob demanda algumas de nossas páginas e componentes.
- Compressão: Produzimos ativos comprimidos (gzip e brotli) para nossos builds de produção.
A migração
Começamos a migração criando um novo projeto Vite e brincando com ele para ver como funciona. O processo foi tranquilo 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ó precisamos 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, em nosso tsconfig.json
:
Só precisamos adicionar a mesma resolução em nosso vite.config.ts
:
Observe que o caminho de substituição deve ser um caminho absoluto, enquanto é relativo à raiz do projeto. Alternativamente, você pode 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 habilitar o React Fast Refresh. Usamos o plugin @vitejs/plugin-react, que é fornecido pela equipe do Vite e tem excelente suporte para recursos 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 quanto adicionar o plugin à configuração do Vite:
No entanto, não especificamos em que condição os SVGs deveriam ser convertidos em componentes React, então todas as importações foram convertidas. O plugin oferece uma configuração padrão melhor: converter apenas os SVGs que são importados com a extensão .svg?react
. Atualizamos nossas importações de acordo.
Módulos SASS
Embora o Vite tenha suporte embutido para módulos SASS, há uma coisa com a qual precisamos nos preocupar: como os nomes das classes são formatados. Pode ser problemático para os usuários e nossos testes de integração se os nomes das classes não forem formatados consistentemente. A configuração de uma linha no vite.config.ts
pode resolver o problema:
Por sinal, Parcel e Vite têm diferentes maneiras de importar arquivos SASS:
A sintaxe * as
, no entanto, funciona no Vite, mas causará a perda de nomes de classes modularizadas quando você usar chaves dinâmicas para acessar o objeto styles
. Por exemplo:
Suporte a MDX
Como o Vite alavanca o Rollup por baixo dos panos, podemos usar o plugin oficial @mdx-js/rollup para suportar MDX, bem como seus plugins. A configuração parece assim:
O plugin remarkGfm
é usado para suportar o GitHub Flavored Markdown, e o plugin rehypeMdxCodeProps
é usado para passar as props para os blocos de código nos arquivos MDX, como o que Docusaurus faz.
Suporte ao Mermaid dentro do MDX
Gostaríamos de usar diagramas Mermaid em nossos arquivos MDX como outras linguagens de programação. O uso deveria ser tão simples quanto outros blocos de código:
Deveria ser renderizado como:
Como nosso aplicativo suporta temas claro e escuro, codificamos um pouco para fazer os diagramas Mermaid funcionarem com o tema escuro. Um componente React foi criado:
useTheme
é um hook customizado para obter o tema atual do contexto. A biblioteca mermaid
é importada de maneira assíncrona para reduzir o tamanho do carregamento inicial da página.
Para o bloco de código no arquivo MDX, temos um componente unificado para fazer o trabalho:
Finalmente, definimos o provedor MDX como segue:
Carregamento sob demanda
Isso não é uma coisa específica do Vite, mas ainda vale a pena mencionar, pois atualizamos nossas páginas para usar carregamento sob demanda durante a migração, e nada quebrou depois.
O React tem uma função embutida React.lazy
para carregar componentes sob demanda. No entanto, pode causar alguns problemas quando você está iterando rapidamente. Criamos 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 interessante chamado vite-plugin-compression para produzir ativos comprimidos. Ele suporta tanto compressão gzip quanto brotli. A configuração é simples:
Chunks manuais
Um ótimo recurso do Vite (ou do Rollup subjacente) são os chunks manuais. Enquanto o React.lazy
é usado para carregar componentes sob demanda, podemos ter mais controle sobre os chunks ao especificar os chunks manuais para decidir quais componentes ou módulos devem ser empacotados juntos.
Por exemplo, podemos primeiro usar o vite-bundle-visualizer para analisar o tamanho do bundle e as dependências. Em seguida, 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 irá empacotar seu código-fonte no modo de desenvolvimento (incluindo dependências vinculadas no mesmo monorepo) e tratará cada módulo como um arquivo. Para nós, o navegador carregará centenas de módulos pela primeira vez, o que parece loucura, mas na verdade está tudo bem na maioria dos casos. Você pode ver a discussão aqui.
Se isso for um problema para você, uma solução alternativa, embora não perfeita, é listar as dependências vinculadas na opção optimizeDeps
do vite.config.ts
:
Isso irá "pré-empacotar" as dependências vinculadas e tornar o servidor de desenvolvimento mais rápido. A desvantagem é que o HMR pode não funcionar como esperado para as dependências vinculadas.
Além disso, usamos um proxy que serve os arquivos 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 irá carregar automaticamente os arquivos .env
e substituir as variáveis no código. No entanto, exige que todas as variáveis de ambiente sejam prefixadas com VITE_
(configurável).
Enquanto usávamos o Parcel, ele simplesmente substituía as variáveis process.env
sem verificar o prefixo. Então, encontramos uma solução alternativa usando o campo define
para tornar a migração mais fácil:
Isso nos permite adicionar gradualmente o prefixo às variáveis de ambiente e remover o campo define
.
Conclusão
É isso! Migramos com sucesso nossos três projetos frontend de Parcel para Vite, e esperamos que essa curta história possa te ajudar em sua migração. Aqui está como ficou a configuração no final: