Sabemos que aplicações desenvolvidas com frameworks são um pouco mais pesadas para o acesso inicial, e hoje veremos algumas técnicas para amenizar isso e tornar nossas aplicações desenvolvidas com Vue mais performáticas, trazendo uma experiência melhor para o usuário final.
Senhoras e senhores, está no ar mais um artigo da equipe Vila do Silício!
Nos últimos dias tive algumas dificuldades com a geração de build para produção com Vue.js. Pela falta de conhecimento, tínhamos comentado a parte de “production” de nosso Webpack, buildando nossa aplicação final da mesma forma que a versão de desenvolvimento (um erro grotesco), então busquei estudar e entender um pouco mais sobre o Webpack.
O Webpack, segundo sua documentação, é um empacotador de módulos estáticos para aplicativos JavaScript modernos. Quando o webpack processa seu aplicativo, ele cria internamente um gráfico de dependência que mapeia todos os módulos que seu projeto precisa e gera um ou mais pacotes configuráveis.
Por padrão, quando utilizamos o Vue-cli para iniciar nossas aplicações Vue, ele já configura previamente o Webpack, possibilitando-nos apenas a melhoria da performance e cache da aplicação através de algumas técnicas.
O arquivo gerado pelo Vue-cli é o webpack.config.js. Nele, há algumas regras para guiar nosso empacotador. Na imagem abaixo mostramos como nosso arquivo já vem configurado:
Entrada
Vamos entender um pouquinho sobre este arquivo. Para começar, nosso arquivo precisa de um ponto de entrada na qual irá indicar qual módulo o webpack deve utilizar para construir seu gráfico de dependência interno.
A partir daí ele também verificará de quais outros módulos e bibliotecas esse ponto de entrada depende (direta e indiretamente). No nosso caso, este ponto de entrada é o entry: ‘./src/main.js’.
Saída
A propriedade de saída informa ao webpack onde emitir os bundles que ele cria e como nomear esses arquivos. O padrão de arquivo de saída que o Vue-cli cria é ‘.build.js’ e a pasta para qualquer outro arquivo gerado é a ‘./dist’.
Porém, como disse inicialmente, o Vue-cli já nos traz o webpack bem otimizado, então iremos focar no trecho que possui o process.env.NODE_ENV === ‘production’. Neste trecho o empacotador Webpack faz algumas modificações para a versão final da build:
Vamos entender o que webpack faz ao buildar nossa aplicação para produção. Ele utiliza alguns plugins para a otimização da aplicação final. Dentre, eles tem o UglifyJsPlugin, que faz a compressão de nossos arquivos JavaScript.
Se gerarmos uma versão de build com o código atual, a versão gerada será assim:
Mas por qual motivo ele gerou dois arquivos?
O Lazy Loading
Ao criar aplicativos com um bundler, o pacote JavaScript pode se tornar muito grande e, portanto, afetar o tempo de carregamento da página. Seria mais eficiente se pudéssemos dividir os componentes de cada rota em um bloco separado e apenas carregá-los quando a rota for visitada.
Combinando o recurso de componente assíncrono do Vue e o recurso de divisão de código do webpack, é trivialmente fácil carregar componentes de rota de forma lenta.
Nosso arquivo routes.js faz isso desta forma:
O componente Card não será baixado pelo navegador ao iniciar a aplicação, e sim quando ele realmente for necessário para o funcionamento (neste caso, na rota /card).
CommonsChunkPlugin
O comonsChunkPlugin é um recurso que cria um arquivo separado (conhecido como um bloco), consistindo em módulos comuns compartilhados entre vários pontos de entrada. Ao separar módulos comuns de pacotes, o arquivo em partes resultantes pode ser carregado uma vez inicialmente e armazenado em cache para uso posterior.
Isso resulta em otimizações de velocidade de carregamento de páginas, pois o navegador pode servir rapidamente o código compartilhado em cache, em vez de ser forçado a carregar um pacote sempre que uma nova página é visitada.
No nosso caso, esse bloco irá armazenar toda a nossa pasta node_modules, que são todas as nossas dependências de nossa aplicação Vue, que normalmente ao ir para produção não será modificada, podendo ficar em cache. Para isso, adicionaremos em nosso arquivo o seguinte plugin:
Ao gerar uma nova versão, ele irá adicionar em um arquivo vendor.js todas as dependências da node_module usadas no projeto. Geramos uma nova build com o comando npm run build e a nova saída será:
É possível ver que o tamanho do arquivo vendor.js é muito superior aos outros, pelo fato deste arquivo conter todas as dependências.
Mas agora com a técnica de chunk ele será carregado apenas no primeiro acesso, e depois utilizado já em cache (sem a necessidade de baixa-lo novamente).
Fingerprinting
Agora teremos de nos preocupar com o cache do navegador do cliente. Como quebramos o cache de um navegador?
Por padrão, somente quando um arquivo em cache expira, ou quando o usuário limpa manualmente o cache, o navegador solicitará o arquivo novamente a partir do servidor.
O arquivo será baixado novamente se o servidor indicar que o arquivo foi alterado (caso contrário, o servidor retornará HTTP 304 Not Modified).
Para salvar uma solicitação de servidor desnecessária, podemos alterar o nome de um arquivo toda vez que seu conteúdo for alterado para forçar o navegador a baixá-lo novamente. Para isso, podemos criar a “Impressão Digital” ao nome do arquivo, acrescentando um hash.
O plugin Common Chunks emite “chunkhash” que é atualizado se o conteúdo do arquivo for alterado. Assim, o webpack pode anexar esse hash aos nomes dos arquivos quando eles são exibidos. Faremos isso no início do nosso arquivo webpack.config.js, deixando-o desta forma:
Criamos uma variável env para identificar através do process.env.NODE_ENV se estamos rodando o comando development ou production. Estes comandos são enviados através de nosso arquivo packge.json já pré-configurado pelo Vue-cli:
E alteramos também o filename através de um if ternário, identificando se o env for de produção, para gerar o nome de nossos arquivos com o chunkhash (nossa identidade digital). O resultado após gerarmos nossa nova build é o seguinte:
É claro que se ficarmos adicionando um hash ao nome de nossa build teremos que ficar atualizando a referência do arquivo no index.html. Caso contrário, o navegador não encontrará o arquivo de destino, pois estará ainda com o build.js que vem por padrão.
Auto injetar arquivos de compilação
Isso seria muito trabalhoso para ser feito manualmente, então utilizaremos o plugin html-webpack-plugin. Primeiro iremos instalar em nosso projeto através do comando:
npm install html-webpack-plugin --save -dev
Após a instalação é necessário importá-lo em nosso arquivo webpack.config.js:
Depois adicionaremos o plugin na build que será gerada no modo de produção:
Ao rodar o nosso comando de build novamente, nossa pasta dist irá conter agora uma cópia do nosso arquivo index principal, mas com a injeção de nossos novos scripts após a build com o hash name.
Por fim, precisaremos apenas remover o antigo arquivo de build,js de nosso novo html gerado, pois não é mais utilizado. Já os arquivos com nossa identidade digital no nome já são inseridos automaticamente.
Na imagem abaixo mostro os arquivos que são baixados numa aplicação em produção, com o gzip ativado no servidor:
Essas são algumas técnicas para otimizar o carregamento de nossas aplicações Single Page Applications desenvolvidas com Vue.js e Webpack.