Migrando uma Aplicação de Grande Porte do Vue 3 para o Vite
Atualmente, trabalho como engenheiro de software na Capim, e uma das iniciativas mais interessantes que temos semestralmente é o que chamamos de Contrato Capim. Basicamente, trata-se de um compromisso com ações de desenvolvimento pessoal ou profissional que desejamos realizar ao longo do semestre.
Um dos itens em que me comprometi foi melhorar a experiência de desenvolvimento do nosso principal projeto front-end, além de otimizar o tempo de build e merge das PRs. Bem, confesso que não planejei os detalhes das ações ou como implementá-las, mas sempre que posso reservo 20% do meu tempo semanal para estudar melhorias e contribuir com a evolução da codebase. Faço isso a muito tempo desde outras empresas que passei, sempre me ajudou no futuro.
Conhecendo um pouco mais a Capim
A Capim nasceu como uma fintech oferecendo serviços financeiros como, por exemplo, financiamento odontológicas e passou a ter também um SaaS para seus já clientes "clínicas odontológicas" terem funcionalidades como agenda, controle de paciente, maquininha de cartão (também temos) e muitas outras funcionalidades.
Toda a aplicação hoje roda em um monólito, com uma base de código bem grande, que foi iniciada no Vue 2
e posteriormente migrada para Vue 3
.
Problemas que enfrentávamos no momento
- O tempo de build era muito longo.
- O live-reload era demorado e recarregava a tela inteira.
- Executar todos os testes de unidade localmente era inviável, mesmo em máquinas mais fortes demorava muito. Temos mais de 6k de testes só de unidade.
Como conheci o vite?
Procurando como resolver os problemas já citados encontrei o Vite
é uma ferramenta de build para aplicações front-end. E segundo sua própria documentação abre aspas O nome vem do francês "vite", que significa rápido
, e foi exatamente o que me pareceu.
Foi criado para resolver problemas comuns enfrentados em projetos grandes, especialmente aqueles que dependem de ferramentas de bundling mais antigas, como Webpack
.
Principais benefícios
- Build mais rápido e eficiente
- O
Vite
utiliza uma abordagem diferente para construir e servir o projeto. Durante o desenvolvimento, ele nãoempacota
toda a aplicação em um único arquivo como o Webpack. Em vez disso, ele carrega módulos de forma incremental usando oES Modules
nativo do navegador.
Primeiro problema: Build muito longo:
- O tempo de build diminuiria drasticamente.
Segundo problema: Experiência de desenvolvimento:
Diferentemente de outras ferramentas, o Vite
faz Hot Module Replacement (HMR)
em nível de módulo. Isso significa que apenas os módulos alterados são atualizados no navegador, sem a necessidade de recarregar toda a página, ou seja, alterações no código seriam refletidas quase instantaneamente no navegador.
- Feedback instantâneo enquanto codamos.
- Fluxo de trabalho mais produtivo e menos frustrante.
Então resumindo, iriamos resolver exatamente os dois problemas
Apresentando o Vite para o time de engenharia
Mais uma das coisas legais que temos na Capim são as Teck Talks
, apresentações realizadas, em média, quinzenalmente, onde qualquer desenvolvedor, independentemente do nível, pode compartilhar um assunto ou apresentar algo relevante para a empresa. Agendei uma Tech Talk sobre o Vite
e preparei uma pequena POC
para demonstrar como ele funcionava.
Na minha apresentação, destaquei os problemas que enfrentávamos com nosso build atual, introduzi o Vite
como solução e expliquei como ele poderia resolver essas questões. E claro, mostrei a POC
rodando ao vivo.
Posteriormente em uma das nossas reuniões da Guilda de Front-end, mais uma coisa legal que temos aqui, batemos o martelo e importante foi decidido em time que iriamos migrar nosso build do vue-cli
para o vite
.
Problemas enfrentados na pesquisa
Falta de artigos sobre migração em projetos reais principalmente de grande porte. A maioria abordava apenas aplicações pequenas e simples.
Plano de migração
Após entender o funcionamento do Vite
e criar algumas POC`s, deixo aqui alguns dos artigos, posts que me ajudaram:
- https://vite.dev/guide/
- https://srivastavaankita080.medium.com/vue-cli-vite-migration-e1aba37e649d
- https://vueschool.io/articles/vuejs-tutorials/how-to-migrate-from-vue-cli-to-vite/
Dividi então a migração em diversas tarefas menores, todas apontadas para uma branch base.
Corrigir imports sem .vue
Atualizar todos os imports de componentes Vue para incluir a extensão .vue.
ex:
import HelloWorld from components/HelloWorld.vue
Ajustar imports com require
Imports com require
não são suportados no Vite
, então precisei atualizá-los para o padrão ES Modules
.
Separar libs externas
Arquivos de bibliotecas gerados por terceiros foram movidos para a pasta public e importados globalmente com tags <script>
.
Passo importante para evitar erros no build ou alertas.
OBS: É um projeto grande que passou por diversos contextos, desenvolvedores e momentos, sei que não é o ideal, mas já estava assim, então não é a primeira vez que vi e acho que não vai ser a última onde arquivos de libs externas eram adicionados e importados diretamente no projeto. Acho importante citar isso para evitar julgamentos desnecessários.
Ajustar componentes carregados por demanda
Um dos beneficios extra do Vite
é que ele avisa sobre componentes muito grandes. Então foi muito bom para mapear
e ajustar seu import para ser usado junto do defineAsyncComponent
.
Migrar variáveis de ambiente
As variáveis de ambiente no Vite
devem começar com VITE_
e ser acessadas via import.meta
. Então esse passo
inicialmente seria um desafio. No projeto durante o tempo variáveis de ambiente foram implementadas de diversas
maneiras e muitas estavam espalhadas pela code base. E a questão, não é só fazer o clássico find-replace
.
Dependendo do servidor onde se está hospedado, nosso caso, precisa se alterar variável a variável. Muito trabalho,
e potenciamente com risco de erros.
A solução foi um plugin que mantém a compatibilidade com process.env
e VUE_APP
chamada vite-plugin-env-compatible. Instalação bem simples.
De bônus para padronizar, adicionei uma regra de LINT
que gera um erro ao utilizar ou criar env-vars
da maneira antiga.
A migração do vite de fato
Aqui, sem muitos segredos: em resumo, foi necessário remover bibliotecas que não eram mais utilizadas, de acordo com os guias, e instalar o Vite
, no nosso caso, a versão 6. Atualizamos alguns pacotes para serem compatíveis com o Vite
e substituímos o vue-cli
pelo vite
.
Removemos o arquivo vue.config
e configuramos o vite.config
, mantendo algumas configurações personalizadas do Vite
, como, por exemplo, a porta de desenvolvimento local, import alias, bibliotecas externas para o Rollup
ignorar, arquivos ou diretórios que não queremos que o Vite
observe, e alguns componentes globais do nosso Design System.
É um pouco difícil dizer claramente o que cada projeto precisará remover ou atualizar além do recomendado, pois cada um tem suas particularidades, tamanho, etc. Mas, se fosse para resumir, diria o seguinte:
- O
Vite
não utiliza oBabel
, e se você não pretende usar o Jest, como foi o nosso caso, pode remover qualquer biblioteca ou configuração relacionada aoBabel
. - Não é mais necessária uma biblioteca adicional para suporte ao
Sass
noVite
. Portanto, bibliotecas comosass-loader
podem ser removidas, já que oVite
vem configurado para transpilar esses arquivos no build. - Caso seu projeto utilize o
i18n
, como o nosso, a versão compatível deve ser superior à 10, com pequenos ajustes no setup.
Instalar e configurar o Vitest
Comecei removendo o Jest
, incluindo qualquer plugin relacionado a ele, e instalamos o Vitest
. Após isso, bastou substituir os scripts.
Partilarmente nesse projeto, temos muitas configurações personalizadas no Jest
que precisaram ser mantidas também no Vitest
, como plugins, import aliases,
dependências, arquivos de setup, mocks globais e pastas que queríamos excluir, como documentações.
O Vitest
utiliza o ambiente happy-dom
, que, segundo benchmarks, é muito mais rápido que o jsdom
. No entanto, com ele, quase todos os nossos testes falharam.
Para manter a compatibilidade, decidimos continuar utilizando o jsdom
.
Fizemos um pequeno ajuste em um dos nossos arquivos globais de mock para evitar ter que substituir em milhares de testes:
import { vi, describe } from vitest
globalThis.vi = vi
globalThis.jest = vi
globalThis.context = describe
Com isso, mocks como jest.fn()
continuaram funcionando normalmente.
Como nem tudo são flores, o Vitest
não é o Jest
e possui muitas diferenças. Foi necessário ajustar cerca de 400 arquivos, com aproximadamente 3.000 testes
falhando após a migração. Grande parte desses problemas era causada por particularidades do Vitest
, como funções que se comportam de maneira diferente, ex
o spyOn
, que precisou ser atualizado.
Essa é uma tarefa meio desesperadora, como fazer para atualizar todos os testes no tempo que tenho e garantir que novos testes não entrem errado e seja um problema no momento final da migração?
Em uma das reuniões da guilda, apresentei as dificuldades e demonstrei ao time como criar mocks e observar a execução e o retorno de funções de forma que não iria
gerar um erro no Vitest
. Uma das principais diferenças é que, no Vitest
, os spy
precisam ser declarados antes do render do componente, por exemplo. Tivemos
outras diferenças, mas não foram significativamente grandes.
Esse foi o passo mais demorado de toda a migração, como o tempo que dedicado não é muito durou em torno de um pouco mais de um mês.
Ajustes finais
Instalei também dois plugins adicionais no Vite
para otimizar o bundle gerado durante o build e melhorar o desempenho geral da aplicação. São eles:
- vite-plugin-compression: Responsável por comprimir os arquivos do bundle
- vite-plugin-imagemin: Utilizado para comprimir imagens
Existem diversos outros, mas para aquele momento achei relevante já ter eles desde o dia zero da migração orque eles trouxeram uma diferença significativa no tamanho do bundle final
Virada de chave.
Agendei o dia do deploy e bloqueamos a branch principal. Em seguida, fizemos o merge da branch base da migração na branch de staging, e todos os times se uniram para testar a aplicação.
Todos os testes automatizados que temos Cypress
, Vitest
e Snapshot
estavam ok e surpreendentemente, os únicos problemas identificados durante os testes manuais foram
erros que já existiam, e aproveitando, corrigidos muitos deles. Foi bem legal e colaborativo, com a participação de muitos outros desenvolvedores.
Subimos a nova versão em produção e foi sem ocorrências relatadas.
Concluindo
Uma migração de um caso caso real, baseado em uma aplicação de grande porte e utilizada diariamente por milhares de clínicas. O resultado final foi melhor que o planejado, porque, além de ganharmos em experiência de desenvolvimento com todos os pontos já citados aqui, temos um ganho consideravel relacionado a perfomance agora com os chunks do bundle divididos em partes menores, não é mais necessário carregar tudo de uma vez para abrir a aplicação.