Front End

18 jan, 2019

Alta performance de aplicações Vue com WebPack

Publicidade

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:

Webpack configurado pelo vue-cli

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’.

Entrada webpack

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’.

Saída webpack

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:

webpack production

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:

build vue webpack

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:

Routes.js

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:

webpack chunk

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á:

build com chunk

É 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:

webpack build com hash no nome

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:

package.json scripts

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:

build com chunkhash

É 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:

import html-webpack-plugin

Depois adicionaremos o plugin na build que será gerada no modo de produção:

Adicionando plugin htmlWebpackPlugin em production

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.

build com index.html inject

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.

Arquivo index.html com inject

Na imagem abaixo mostro os arquivos que são baixados numa aplicação em produção, com o gzip ativado no servidor:

JavaScript produção

Essas são algumas técnicas para otimizar o carregamento de nossas aplicações Single Page Applications desenvolvidas com Vue.js e Webpack.

Referências