Esses números, ai ai ai…

Tunando manualmente o Webpack

De volta em Abril, eu mudei para um cargo de “arquiteto de plataforma” aqui no PayPal. Eu fiquei responsável em olhar na estabilidade, performance e qualidade da nossa estrutura. Uma das primeiras coisas que fiz, foi mapear o tamanho dos bundles do nosso JavaScript ao longo do tempo.

Após algumas semanas, eu notei que nós tínhamos picos em vários pontos do nosso processo de build, que eram quase 1mb. Olhando mais de perto, eu descobri que nós estávamos duplicando coisas por todo lugar.

Com o NPM, fica muito fácil instalar várias versões de um mesmo módulo. Para código server side, não há problema, mas quando empacotamos para web, isso é um grande problema! Webpack disponibiliza maneiras básicas de inspecionar os arquivos gerados, mas, para chegar até eles é preciso de alguns comandos extras:

webpack --display-modules | awk '{print $3$4"  "$2}' | grep -v bytes | sort -n | tail -100

Vou guiar você no que está acontecendo aqui:

  • display-modules é um parâmetro do webpack que irá mostrar todos os módulos importados no build.
  • awk re-organiza as colunas e delta informação necessária
  • grep está removendo pequenos arquivos desse log
  • sort -n está colocando os arquivos maiores embaixo
  • tail está mostrando apenas os últimos 100 (maiores)

E, com essa mágica, você gera uma lista como essa:

41.7kB  ./lib/bootstrap-datepicker.js
46kB /Users/jamuferguson/dev/paypal/web/~/jcarousel/dist/jquery.jcarousel.js
48.9kB ./view/wallet/overpanel/bank/addBank.js
52.1kB /Users/jamuferguson/dev/paypal/web/~/bootstrap-tour/build/js/bootstrap-tour-standalone.js
52.9kB /Users/jamuferguson/dev/paypal/web/~/underscore/underscore.js
60.3kB /Users/jamuferguson/dev/paypal/web/~/exports-loader?requirejs,define!./lib/require-2.1.6.js
61kB /Users/jamuferguson/dev/paypal/web/~/backbone/backbone.js
135kB /Users/jamuferguson/dev/paypal/web/~/moment/moment.js
244kB /Users/jamuferguson/dev/paypal/web/~/lodash/dist/lodash.compat.js
259kB /Users/jamuferguson/dev/paypal/web/~/jquery/dist/jquery.js
273kB ./lib/jquery-1.10.2.js

Novidade: Parece que já existe um comando no webpack para isso:

webpack --display-modules --sort-modules-by size

A lista gerada é um pouco mal-arranjada, mas ordena as coisas de maneira correta!

Um log meio "sujo", mas, muito útil!

Investigando a causa das duplicações

Caso você suspeite que você tenha mais de uma versão de um módulo, você pode usar o npm ls para ver como esse módulo é incluído no seu projeto.

Apesar do npm ls mostrar tudo dentro do seu diretório node_modules, webpack provavelmente não está incluindo cada um deles no seu build. Ele só irá incluir arquivos que estão sendo chamados pelo require() no seu código.


Evitando as duplicações de módulos

Nesse ponto, você deve estar sentido uma fadiga de usar o terminal. Você usou vários comandos, mas, não chegou a nenhum lugar, como você pode realmente diminuir o tamanho do seu build?

No exemplo acima, este projeto está diretamente dependente no React 15, mas uma de suas dependências é o react-intl@1.2.2 que tem uma dependência no React 0.13.

A solução neste caso, é trocar a dependência do react-intl por uma biblioteca de internacionalização que tenha suporte a versão mais recente do React. Neste caso, não foi um grande problema porque acabamos escrevendo uma versão simplificada para solucionar o problema.

Ainda bem que, vários dos problemas no nosso build foram fáceis de resolver. Aqui alguns exemplos:

Nossa versão do lodash era muito grande

244kB /Users/…/dev/paypal/web/~/lodash/dist/lodash.compat.js

Existem vários plugins por aí que optimizam o build do lodash para incluir apenas as partes que você usa. Isso pode reduzir drasticamente o tamanho do seu build final. Se você estiver usando Babel, eu recomendo usar:

Ou, se não:

Antes desses plugins aparecerem, nós usávamos uma regra do ESLint, no-restricted-module, para banir o lodash do nosso diretório public/ (em favor do underscore). Isso nos preveniu de importar ambos no build final.

Mas eu uso várias versões do jQuery

259kB /Users/jamuferguson/dev/paypal/web/~/jquery/dist/jquery.js
273kB ./lib/jquery-1.10.2.js

Nesse caso, nós temos uma versão local do jQuery (que estávamos usando como alias desde sempre), mas agora, nós também temos um módulo que tem uma dependência explícita na última versão do jQuery.

A solução simples para isso é remover sua versão local e usar apenas a instalada pelo NPM.

Mas, e se você não puder atualizar para a última versão do jQuery devido à alguma incompatibilidade? Se isso for um problema, tente usar o plugin de migração do jQuery, ou você pode encorajar os criadores das bibliotecas a serem mais inclusivos nas versões no package.json:

“jquery”: “¹ || ^2”

Essa sintaxe permite um pacote a usar tanto uma versão 1.x quanto uma versão 2.x. Em vários casos, isso é o suficiente para criar a compatibilidade que você precisa.


Finalizando

Meu time conseguiu reduzir em quase 800kb o tamanho do bundle final usando essas técnicas. Outros relataram uma redução de vários kbs no Moment.js usando alguns plugins do webpack.

Você está tento problemas em deixar seu build final pequeno? Quais as outras técnicas que você usa para solucionar esses problemas?


Créditos