webpack para alta performance front-end

Otimizando suas configurações para melhorar seu bundle final

Quer ficar por dentro das novidades do webpack? Se inscreva já https://webpack.academy/

Créditos

Mais e mais desenvolvedores estão usando o webpack para facilitar o seu processo de build, mas mesmo em 2017, muitos sites ainda não aproveitam as melhores configurações de desempenho que o webpack oferece. O webpack tem por padrão algumas opções de otimizações para lá de impressionantes, e que você pode não saber de início. Aliás, você está utilizando tudo isso?

Neste artigo, iremos abordar 7 otimizações fáceis do webpack, que irá melhorar seu aplicativo deixando-o mais rápido aos usuários, independentemente do que você esteja usando (e uma opção de fallback, quando não for possível usar importações dinâmicas). Em um aplicativo de exemplo, começando com nenhuma otimização, iremos compactar nosso JS em 700% e melhorar o armazenamento com cache inteligente para atualizações rápidas.

A melhor parte? Você pode ter um site com carregamento mais rápido em apenas 15 minutos implementando algumas dessas dicas. Você pode, realisticamente, implementar todas essas dicas em cerca de 1 hora.

Do que iremos falar (mais 🚀 === mais velocidade):

  1. 🕙 1 min: Scope Hoisting (novidade do webpack 3) 🤷
  2. 🕙 2 min: Minificação e Uglificação com UglifyJS2 🚀🚀🚀
  3. 🕙 15 min+: Importações Dinâmicas e Módulos sob Demanda, carregando seu código quando for preciso 🚀🚀🚀🚀🚀
  4. 🕙 5 min: Hashes Determinísticos para um melhor cache 🚀🚀
  5. 🕙 10 min: CommonsChunkPlugin, desduplicação e cache de vendors 🚀🚀
  6. 🕙 2 min: Offline Plugin para webpack 🚀🚀
  7. 🕙 10 min: webpack Bundle Analyzer 🚀🚀🚀
  8. 🕙 2 min: Múltiplos pontos de entrada automaticamente com CommonsChunkPlugin para os casos especiais quando não for possível usar importações dinâmicas 🚀

Esse artigo assume que você esteja usando webpack 3.x (3.x tem uma impressionante taxa de 98% de atualização sem dores de cabeça da 2.x, ou seja, raramente você não terá problemas ao atualizar). Se você ainda está na versão 1.x, você precisa atualizar para poder tirar vantagem da automatização automática de tree-shaking e dead code elimination.

💻 Códigos para nosso exemplo

Todos os exemplos, funcionando, de todos esses conceitos desse artigo, podem ser encontrados nesse repositório. Haverá também links em cada seção. Isso é cumulativo sempre que possível, então os recursos adicionados em um passo serão transferidos para o próximo.


1. Scope Hoisting

  • Estimativa de tempo: 🕙 1 min
  • Estimativa de melhoria: 🤷

O webpack 3, lançado em junho de 2017, foi lançado com muitas melhorias por debaixo do capô, bem como um modo de compilação mais otimizado chamado “levantamento de escopo”, scope hoisting, que salvou, para algumas pessoas, preciosos KB. Para habilitar, adicione a seguinte configuração do webpack, para produção:

const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
],
};

Alguns relataram uma redução de quase 50% no tamanho do bundle final, mas, no nosso exemplo 1, não notei nenhuma alteração (era tecnicamente um ganho de 6 bytes, o que é insignificante). Pelo que parece, o scope hoisting, produz um resultado melhor para aplicativos legados com centenas de módulos e não para projetos de exemplo, como nosso teste. Parece não haver uma desvantagem real ao usar, então eu ainda recomendaria adicioná-lo se não gerar erros. O artigo de lançamento do webpack 3, explica em mais detalhes este recurso.

  • 💻 Exemplo 1: Scope hoisting (ver código)
  • 📦 Resultado: 🤷 É uma simples adição, sem desvantagens e potencial retorno futuro. Por que não?

2. Minificação e Uglificação

  • Estimativa de tempo: 🕙 2 min
  • Estimativa de melhoria: 🚀🚀🚀

Uglificação é sempre relacionado à otimização, e nunca foi tão fácil de aproveitar do que com o webpack. webpack é construído diretamente com ele! Embora seja uma das otimizações mais acessíveis, é muito fácil para um desenvolvedor com pressa esquecer de uglificar ao enviar arquivos para a produção. Eu pessoalmente vi mais de um site usando webpack com código não uglificado, em produção. Então, em nossa jornada de otimização, esta é a primeira verificação para ter certeza de que está no lugar certo.

Se você é novo na uglificação, a principal coisa para entender é a diferença que ele faz no tamanho do arquivo. Aqui, estou executando o comando padrão do webpack em nosso exemplo:

          Asset     Size  Chunks                    Chunk Names
index.bundle.js 2.46 MB 0 [emitted] [big] index

2.46MB. Caramba! Se inspecionarmos esse arquivo, está cheio de espaços, quebras de linha e comentários — tudo o que não precisa chegar em produção. Para corrigir isso, tudo o que precisamos fazer, é passar o parâmetro -p:

webpack -p

Vamos ver como -p afetou o nosso build:

          Asset     Size  Chunks                    Chunk Names
index.bundle.js 1.02 MB 0 [emitted] [big] index

1.02MB. Isso é uma redução em tamanho de 60%, e nós não mudamos nenhuma parte do código, só precisei apertar duas teclas do meu teclado! O parâmetro -p é uma abreviação de production e habilita a minificação e uglificação, e automáticamente, outras várias melhorias para código de produção.

===

📝 Nota

Apesar das aparências do -p, ele não muda a variável de ambiente do Node para “production”. Quando você configurar o script na sua máquina, lembre-se:

NODE_ENV=production PLATFORM=web webpack -p

Vale ressaltar que algumas bibliotecas (React e Vue, por exemplo) foram projetadas para descartar os recursos de desenvolvimento e teste quando forem empacotados com “production”, resultando em tamanhos menores de arquivos e tempos de execução mais rápidos. Se você executar isso no nosso projeto de exemplo, o nosso bundle final será 983 KB em vez de 1,06 MB. Em algumas outras configurações, porém, pode não haver diferença de tamanho — sendo responsabilidade das bibliotecas que estão sendo usadas.

===

💁 Dica

Para rodar esses comandos mais rápido, adicione o bloco ”scripts” no seu package.json, para você, ou seu servidor, podem usar npm run build como um atalho:

"scripts": {
"build": "webpack -p"
},

===

Configurando UglifyJS

A configuração padrão do Uglify é boa o suficiente para a maioria dos projetos e a maioria das pessoas, mas se você quiser espremer cada pequena gota de código desnecessário de seu bundle final, adicione um webpack.optimize.UglifyJsPlugin na sua configuração de produção:

plugins:[
new webpack.optimize.UglifyJsPlugin({/* opções aqui */}),
],

Para obter uma lista completa das configurações do UglifyJS2, o lugar mais atualizado para isso, é a sua documentação.

===

⚠️🐌 Alerta sobre seu build

Se você, acidentalmente, habilitou uglificação no modo de desenvolvimento, isso irá, significantemente, deixar a compilação do webpack devagar. É melhor deixar essa opção apenas para produção (você pode ver o artigo no site do webpack para configurações de desenvolvimento/produção).

===

  • 💻 Exemplo 2: Não há, mas você pode usar webpack -p no nosso aplicativo de exemplo.
  • 📦 Resultado: No nosso exemplo, diminuímos em 60% o tamanho do arquivo final, com uglificação e minificação padrão. Não é ruim!

3. Importações Dinâmicas e Módulos sob Demanda

  • Estimativa de tempo: 🕙 15 min+
  • Estimativa de melhoria: 🚀🚀🚀🚀🚀

A importação dinâmica são as “jóias da coroa” do desenvolvimento front-end. O Santo Graal. O Arco Perdido. O Templo de Doom — ahm, esqueça esse último; Me animei usando nomes dos filmes de Indiana Jones 😄.

Seja qual for o filme de Harrison Ford, as importações dinâmicas, ou sob demanda, são um grande ponto em otimizações, pois eles conseguem facilmente um dos objetivos centrais do desenvolvimento de front-end: carregar as coisas somente quando elas são necessárias, nem mais cedo, nem mais tarde.

Nas palavras de Scott Jehl, “mais peso não significa mais espera”. Como você entrega seu código aos usuários (sob demanda) é mais importante do que o peso total do seu código.

Vamos analisar o seu impacto, Utilizando webpack -p no nosso exemplo:

          Asset     Size  Chunks                    Chunk Names
index.bundle.js 1.02 MB 0 [emitted] [big] index

Nós temos 1.02 MB de código JS, o que não é insignificante. Mas o problema crucial aqui NÃO é o tamanho. O problema é entegar como um único arquivo. Isso é ruim porque todos os seus usuários devem baixar todo o pacote antes de verem alguma coisa na tela. Nós certamente podemos fazer melhor, quebrando esse arquivo e permitindo que algo aconteça mais cedo.

Importação Dinâmica, Parte 1: Configuração do Babel

O Babel e o plugin de Importação Dinâmica são ambos os requisitos necessários. Se você não estiver usando o Babel em seu aplicativo, você precisará dele para esse recurso. Para configurar pela primeira vez, instale o Babel:

yarn add babel-loader babel-core babel-preset-env

e atualize seu webpack.config.js para usar Babel nos seus arquivos JS:

module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
],
},

Uma vez configurado, vamos colocar as importações dinâmicas para trabalhar, instale o plugin Babel:

yarn add babel-plugin-syntax-dynamic-import

E, em seguida, habilite o plugin modificando ou criando um arquivo .babelrc na raiz do projeto:

{
"presets": ["env"],
"plugins": ["syntax-dynamic-import", "transform-react-jsx"]
}

Alguns preferem adicionar opções do Babel no webpack, para que tudo fique no mesmo arquivo. Eu simplesmente prefiro o arquivo .babelrc separado para uma configuração webpack mais limpa. De qualquer forma, funciona.

===

💁 Dica

Se você está acostumado a ver es2015 como o preset do Babel ao invés do env, considere mudar. env é uma configuração mais simples que pode transpilar automaticamente alguns recursos com base em sua browserlist (deve ser familiar para usuários do Autoprefixer para CSS).

===

Importação Dinâmica, Parte 2: import()

Depois de ter configurado a Babel, iremos dizer ao nosso aplicativo o que e onde queremos carregamento sob demanda. Isso é tão simples como substituir:

import Home from './components/Home';

por:

const Home = import('./components/Home');

Não parece muito diferente, certo? Se você já viu antes o require.ensure, ele foi descontinuado em favor do import().

Alguns frameworks como Vue, já suportam isso por padrão. Porém, como nosso aplicativo de exemplo usa React, nós precisamos usar um high order component chamado react-code-splitting. Isso nos economiza ~7 linhas de código, cuidando da importação e com os métodos de ciclo de vida de renderização do React, para nós. Mas não é mais do que açúcar sintático, e a funcionalidade principal é 100% do import() do webpack. É assim que o nosso aplicativo deve ficar:

import React from 'react';
import Async from 'react-code-splitting';
const Nav = () => (<Async load={import('./components/Nav')} />);
const Home = () => (<Async load={import('./views/home')} />);
const Countdown = () => (<Async load={import('./views/countdown')} />);

O webpack transforma cada import() em uma divisão de código dinamicamente, agora, vamos ver como fica nosso webpack -p:

    0.bundle.js     222 kB       0  [emitted]         
1.bundle.js 533 kB 1 [emitted] [big]
2.bundle.js 1.41 kB 2 [emitted]
index.bundle.js 229 kB 3 [emitted] index

Ele reduziu o nosso ponto de entrada, index.bundle.js, de 1.06MB para 229kB, uma redução de 80% no tamanho! Isso é significativo, porque é nosso arquivo de entrada. Antes, o evento de paint do navegador não poderia acontecer até que 1,06MB fossem baixados completamente, analisado e executado. Agora, precisamos apenas de 20% do código original para começar. E isso se aplica a todas as páginas do site! Isso não diz que, tecnicamente, o evento de paint é 5× mais rápido, é mais complicado do que isso, mas, no entanto, é um incrível aumento de velocidade com pouco investimento em tempo.

Você pode estar pensando: “Não pode ser, tem que haver mais configuração!”, mas vou te dizer, é extamente só isso!

No nosso exemplo, não foi necessário mudar o nome do arquivo de entrada index.bundle.js, de modo que, precisamos apenas, de uma única tag <script>. O webpack resolve o resto para nós (embora, você possa encontrar um problema de polyfill se precisar suportar um navegador que não ofereça suporte a Promise)!

  • 💻 Exemplo 3: Importações Dinâmicas e Módulos sob Demanda (ver código).
  • 📦 Resultado: Nós temos um aplicativo realizando o evento de paint em apenas 20% do tamanho do pacote original. As importações dinâmicas são, sem dúvida, a melhor otimização que você pode fazer no front-end. Desde que você esteja usando roteamento do lado do cliente para obter melhores resultados.

4. Hashes Determinísticos

  • Estimativa de tempo: 🕙 5 min
  • Estimativa de melhoria: 🚀🚀

Cache é apenas um benefício para os usuários que retornam e não afeta a primeira experiência, que é crítica. Por padrão, o webpack não gera nomes de arquivo com hashes (por exemplo: app.8087f8d9fed812132141.js), o que significa que tudo fica em cache e suas atualizações podem não estar chegando aos usuários. Isso pode quebrar a experiência e causar frustração.

A maneira mais rápida de adicionar hashes no webpack é:

output: {
filename: '[name].[hash].js',
},

Mas há um problema: a cada compilação, um novo hash é gerado, se o conteúdo do arquivo mudou ou não. Se você está usando isso junto de webpack -p para deploy (o que é uma ótima idéia), isso significa que todos os usuários terão que baixar todos os seus arquivos novamente, mesmo se você não alterou uma linha de código.

Nós podemos fazer melhor com hashes determinísticos, hashes que só mudam se o arquivo for alterado.

===

⚠️🐌 Alerta sobre seu build

Os hashes determinísticos diminuirão o tempo de compilação. Eles são uma ótima idéia, mas isso significa que esta configuração deve estar apenas na sua configuração produção.

===

Para começar, vamos adicionar alguns pacotes para nossa configuração de produção:

yarn add chunk-manifest-webpack-plugin webpack-chunk-hash

E na nossa configuração:

const webpack = require('webpack');
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
const WebpackChunkHash = require('webpack-chunk-hash');
const HtmlWebpackPlugin = require('html-webpack-plugin');
/* Configuração compartilhada Dev & Prod */
const config = {
/* … nossa configuração até esse momento */
plugins: [
// /* outros plugins aqui */
//
// /* Descomente para gerar HTML automaticamente */
// new HtmlWebpackPlugin({
// inlineManifestWebpackName: 'webpackManifest',
// template: require('html-webpack-template'),
// }),
],
};
/* Produção */
if (process.env.NODE_ENV === 'production') {
config.output.filename = '[name].[chunkhash].js';
config.plugins = [
// ES6 Array Destructuring, disponível em Node 5+
...config.plugins,
new webpack.HashedModuleIdsPlugin(),
new WebpackChunkHash(),
new ChunkManifestPlugin({
filename: 'chunk-manifest.json',
manifestVariable: 'webpackManifest',
inlineManifest: true,
}),
];
}
module.exports = config;

Nossa condicional process.env.NODE_ENV === ‘production’ irá adicionar os plugins apenas para produção.

===

💁 Dica

No exemplo acima, adicionando yarn add html-webpack-plugin html-webpack-template, e descomentando o plugin comentado, o webpack irá gerar automaticamente o HTML para você. Isso é ótimo se você estiver usando uma biblioteca como React para gerar marcação para você. Você pode até personalizar o HTML, se necessário.

===

Usando webpack -p, você irá notar um novo arquivo chunk-manifest.json, que precisar ser injetado no do seu documento. Se você não estiver usando o plugin de HTML do webpack, você precisa fazer isso manualmente.

<head>
<script>
//<![CDATA[
window.webpackManifest = { /* contents of chunk-manifest.json */ };
//]]>
</script>
</head>

Também tem o manifest.json, que você precisa adicionar através de uma tag <script> também. Uma vez que ambos estejam lá, você pode ficar tranquilo!

  • 💻 Exemplo 4: Hashes Determinísticos (ver código).
  • 📦 Resultado: Os usuários agora podem receber atualizações frequentemente, mas somente os arquivos que tiveram seu conteúdo modificado. Armazenamento em cache resolvido!

5. CommonsChunkPlugin, desduplicação e cache de vendors

  • Estimativa de tempo: 🕙 10 min
  • Estimativa de melhoria: 🚀🚀

Nós cuidamos do nosso armazenamento em cache dos arquivos gerados pelo webpack, mas vamos levá-lo a uma passo adiante e armazenar em cache nossos pacotes vendors para que os usuários não tenham que baixar o arquivo de entrada inteiro novamente se mudarmos uma única linha de código. Para fazer isso, vamos adicionar um item de entrada de vendors para armazenar nossas bibliotecas de terceiros:

module.exports = {
entry: {
app: './app.js',
vendor: ['react', 'react-dom', 'react-router'],
},
};

Usando webpack -p:

           Asset    Size  Chunks                    Chunk Names
index.bundle.js 230 kB 3 [emitted] index
vendor.bundle.js 173 kB 4 [emitted] vendor

Infelizmente, o nosso arquivo de entrada é maior do que deveria ser, e o que está causando isso no processo do webpack é que o React, ReactDOM e React Router estão sendo importados em ambos, index.bundle.js e vendor.bundle.js.

O webpack não é o culpado, afinal, ele fez exatamente o que pedimos. Quando você especifica um arquivo de entrada, você está dizendo ao webpack que você quer que cada arquivo de saída seja independente e completo. O webpack supôs que você estará servindo um ou outro, e não ambos ao mesmo tempo.

No entanto, estaremos servindo ambos simultaneamente, o que exigirá apenas um pouco mais de configuração. Teremos que adicionar CommonsChunkPlugin aos nossos plugins:

const webpack = require('webpack');
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
],

Agora, CommonsChunkPlugin está habilitado, e sabe usar o ponto de entrada fornecido, nesse caso vendor, como base para gerar o CommonsChunk. Você pode ter visto uma configuração com minChunks neste plugin, que podemos omitir aqui porque já dissemos ao webpack exatamente o que está acontecendo nesse ponto de entrada.

===

💁 Dica

A chave name do CommonsChunkPlugin deve coincidir com o nome de um ponto de entrada, nesse exemplo é vendor, caso contrário, voltaremos para o cenário onde estaremos duplicando as bibliotecas de em todos os arquivos de entrada.

===

Como tudo em seu lugar, vamos rodar webpack -p e ver os resultados:

           Asset     Size  Chunks               Chunk Names
index.bundle.js 55.7 kB 3 [emitted] index
vendor.bundle.js 174 kB 4 [emitted] vendor

Você viu o que aconteceu? Nosso novo ponto de entrada teve seu tamanho aproximado aos nossos pacotes de vendors: 174 KB. Nós não estamos mais duplicando o código, no entanto, agora, devemos carregar vendor.bundle.js primeiro em todas as páginas antes do index.bundle.js, que agora é uma dependência onde quer que o index.bundle.js seja necessário:

<!-- vendor vem primeiro! -->
<script src="vendor.bundle.js"></script>
<script src="index.bundle.js"></script>

Agora, sempre que você atualizar o código do seu aplicativo, os usuários só terão que recarregar esse arquivo de entrada de 55,7 KB, ao invés de todos os 174 KB. Esta é uma otimização sólida em qualquer configuração.

===

💁 Dica

Quando você estiver escolhendo os pacotes para mover para essa entrada vendor, tenha em mente:

  • Adicione pacotes necessários para seu aplicativo como um “todo”.
  • Adicione também as dependências menos atualizadas freqüentemente (lembre-se: se uma atualização de vendedor for atualizada, todo o pacote será baixado novamente)
  • Apenas carregue os submódulos comumente usados. Por exemplo, se o aplicativo usa frequentemente ”rxjs/Observable”, mas raramente ”rxjs/Scheduler”, então, só carregue o primeiro! E, independente do que você faça, não adicione todo “rxjs” (ser preguiçoso aqui, irá lhe custar vários KB!)

===

  • 💻 Exemplo 5: Commons Chunk Plugin (ver código).
  • 📦 Resultado: Como qualquer esforço de cache, isso atende principalmente aos usuários que retornam. Se você tem um site ou serviço freqüentemente referenciado, isso é absolutamente essencial.

6. Offline Plugin para webpack

  • Estimativa de tempo: 🕙 2 min
  • Estimativa de melhoria: 🚀🚀

Você já visitou um site em seu celular, quando estava com uma conexão de dados ociosa e você, acidentalmente, desencadeou uma atualização ou o próprio site desencadeou uma atualização? Muita frustração poderia ter sido evitada se o site que, que já havia sido completamente carregado, tivesse uma estratégia de cache melhor. Felizmente, há um plugin de webpack que se tornou um elemento básico na comunidade PWA: OfflinePlugin. Ao adicionar apenas algumas linhas à sua configuração do webpack, seus usuários podem visualizar seu site enquanto estiverem offline.

Vamos instalar o pacote:

yarn add offline-plugin

Adicionar a nossa configuração do webpack:

const OfflinePlugin = require('offline-plugin');
module.exports = {
entry: {
// Adicionando ao ponto de entrada de vendor, porém é opcional
vendor: ['offline-plugin/runtime', /* … */],
},
plugins: [
new OfflinePlugin({
AppCache: false,
ServiceWorker: { events: true },
}),
],
};

E, em algum lugar do seu aplicativo (de preferência no seu arquivo de entrada, antes do código de renderização):

/* index.js */
if (process.env.NODE_ENV === 'production') {
const runtime = require('offline-plugin/runtime');
runtime.install({
onUpdateReady() {
runtime.applyUpdate();
},
onUpdated() {
window.location.reload();
},
});
}

No geral, é uma adição simples, qualquer aplicativo pode obter resultados em uma experiência de usuário significativamente melhor, imagine usuários que usam o metrô diariamente, eles ficam rapidamente fora de serviço. Para obter mais informações sobre os benefícios e como configurar melhor o plugin, para sua necessidade, consulte a documentação.


7. webpack Bundle Analyzer

  • Estimativa de tempo: 🕙 10 min
  • Estimativa de melhoria: 🚀🚀🚀

De todas as opções que cobriremos para otimizar nosso processo de build, esse é, de longe, o menos automático, mas também ajuda a encontrar erros que passam despercebidos e que as otimizações automáticas irão ignorar. Parando para pensar, você deveria começar por essa etapa, afinal, de que outra forma você pode otimizar seu bundle se você não o entende? Para adicionar o webpack Bundle Analyzer, vamos executar:

yarn add --dev webpack-bundle-analyzer

E vamos adicionar em nossa configuração de desenvolvimento:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
config = { /* configurações comuns */ };
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
config.plugins = [
...config.plugins,
new BundleAnalyzerPlugin(),
];
}

Preste atenção no .BundleAnalyzerPlugin no final do require().

Vamos rodar o seguinte comando e abrir nosso localhost:8888:

node_module/.bin/webpack --profile --json > stats.json
Bundle antes da otimização

Aqui, você pode ver os detalhes dos módulos de todo o seu aplicativo, byte por byte. Observe atentamente a seção moment, do exemplo 7. Há vários idiomas agrupados! Todos, para serem exatos. Embora a internacionalização seja uma coisa boa, ainda não estamos prontos para isso no nosso aplicativo de exemplo, portanto, não há uma boa razão para enviar linguagens não utilizadas ao cliente.

                    Asset    Size Chunks             Chunk Names
0.cc206a4187c30a32c54e.js 224 kB 0 [emitted]

Estamos usando a importação dinâmica, o que é ótimo, mas ainda estamos gastando 224 KB no moment. Pesquisando um pouco, encontrei essa solução que me permitiu usar apenas as línguas que eu precisava.

Bundle depois da otimização

De acordo com o analisador, parece muito menor! Mas vamos ver como nosso pacote final ficou:

                    Asset     Size Chunks            Chunk Names
0.4108c847bef03ae9e840.js 62.7 kB 0 [emitted]

Foi diminuido 161 KB! Isso é bem significante! Se nunca tivéssemos executado o webpack Bundle Analyzer, talvez nunca iríamos notar tudo isso no nosso aplicativo, e iríamos simplesmente aceitar isso como peso de dependência. Você pode se surpreender com o quanto uma troca de biblioteca simples ou uma linha de webpack reconfigurada podem economizar em tamanho final!

Com o webpack Bundle Analyzer, você obtém algumas ótimas dicas sobre onde começar a procurar oportunidades de otimização. Comece nos maiores módulos primeiro e avance a pequenos passos, vendo se há algo que você pode otimizar ao longo do caminho. Você pode selecionar melhorar a importação de depêndencia (por exemplo, usar require(“rxjs/Observable”) ao invés de require(“rxjs”))? Você pode substituir bibliotecas grandes por pequenas? Por exemplo, trocar React por Preact)? Existem módulos que você pode remover inteiramente? Fazer perguntas como essas, geralmente pode ter grandes ganhos.

  • 💻 Exemplo 7: webpack Bundle Analyzer (ver código).
  • 📦 Resultado: Descobrimos uma jogada muito bonita em nosso aplicativo, que conseguiu salvar 161 KB em apenas uma dependência. Definitivamente vale a pena o tempo investido.

8. Múltiplos pontos de entrada automaticamente com CommonsChunkPlugin

  • Estimativa de tempo: 🕙 2 min
  • Estimativa de melhoria: 🚀

A última otimização que vamos abordar é uma técnica dos primórdios dias do webpack que, na minha opinião, não é tão necessária como era (se você discordar, por favor, comente — eu adoraria saber como você está usando essa técnica). Esta opção só deve ser usada se o seu aplicativo atender a todos os seguintes:

  • Contém muitos, muitos pontos de entrada em todo o aplicativo
  • Não pode usar importações dinâmicas
  • A quantidade de código proprietário supera as depêndencias de bibliotecas do npm E é dividida em módulos ES6

Se o seu aplicativo não atender a todos esses critérios, eu recomendaria que você retornasse à seção 3. Importações Dinâmicas e Módulos sob Demanda e 5. CommonsChunkPlugin, desduplicação e cache de vendors. Se você cumprir todos os requisitos e esta é a sua única opção, vamos falar sobre seus prós e contras. Primeiro, vamos supor que temos as seguintes entradas em nosso aplicativo (não estamos usando o nosso exemplo anterior, porque esta é uma configuração muito diferente):

module.exports = {
entry: {
main: './main.js',
account: './account.js',
shop: './shop.js',
},
};

Podemos atualizar o CommonsChunk para descobrir as coisas automaticamente:

/* Dev & Prod */
new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
minChunks: 2,
}),
/* Prod */
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),

A única configuração a ser ajustada aqui é minChunks. Isso determina quantos pontos de entrada um módulo deve aparecer, para move-lo para o arquivo commons.js. Se uma biblioteca só apareceu em 1 dos 3, não seria possível. Mas assim que dois pontos de entrada estão importando o mesmo, ele será removido de ambos os módulos e colocado em commons.js.

Mais uma vez, isso só funciona se você não estiver aproveitando as importações dinâmicas. Porque com as importações dinâmicas, conseguimos carregar inteligentemente em cada página, apenas o código que o usuário precisa e nada mais. Mas com esta opção, é um tanto “burra”, no sentido de que não se sabe o que um usuário precisa por página (o que é ruim); Ele apenas agrupa um arquivo comum de recursos, pressupondo que um usuário irá visitar muitas páginas do seu aplicativo (provavelmente não é o caso). Além disso, não é verdadeiramente automático, pois você terá que testar e encontrar a configuração de minChunks mais eficiente com base no fluxo e arquitetura de usuários da sua aplicação.

  • 💻 Exemplo 8: Não temos, pois é uma situação diferente.
  • 📦 Resultado: Não é ruim, mas é melhor usar a importação dinâmica (tópico 3) e a desduplicação e cache de vendors (tópico 5).

Analisando nossas otimizações e conclusão

Cobrimos algumas maneiras novas e poderosas de oferecer uma melhor experiência aos seus usuários. Vamos analisar cada etapa e ver quantos KB conseguimos salvar em nossa aplicação de exemplo:

Resumo das etapas de otimizações
  • A técnica do Commons Chunk Plugin dividiu um arquivo de entrada em 2, e o tamanho combinado de ambos está listado.
  • A técnica do Bundle Analyzer salvou 161 KB em uma análise específica. Essa é uma economia significativa, mesmo que não se aplique a 100% dos usuários.

Você conseguiu incorporar algumas técnicas em sua aplicação? A sua pontuação no Lighthouse melhorou? Você está mais perto do, ilusório, 1-segundo para o evento de paint do navegador?

Aliás, se alguma dica de otimização ficou de fora, por favor, deixe um comentário!

A você, meu muito obrigado!


📚 Leitura adicional e notas

  • Os artigos do Google sobre otimização são brilhantes. Eu não encontrei nenhum outro recurso que exige que você carregue seu site em 1 segundo e, em seguida, forneça excelentes sugestões para alcançá-lo. Se você não tem certeza de como melhorar seu aplicativo, o modelo RAIL desse link é o melhor lugar para começar.
  • Lighthouse, caso você não tenha notado as menções anteriores, esse é a ferramenta de “status quo” atual para medidas de performance. Lighthouse é muito semelhante ao antigo ranking do PageSpeed, ou YSlow, exceto que, é modernizado para aplicativos modernos da web 2017 e mantém suas análises com os padrões mais recentes.
  • O webpack fez melhorias recentes em sua documentação sobre importação dinâmicos, mencionando o truqe do Vue com import(). É animador ver tantas melhorias esse ano!
  • O AggressiveSplittingPlugin do webpack recebe uma menção honrosa aqui, referenciado em um artigo no webpack e HTTP/2 pelo autor do webpack. O plugin foi incluído originalmente neste artigo, mas depois de alguns testes, encontrei as importações dinâmicas como uma solução mais universal e melhor. Esse plugin remove a opção de carregamento sob demanda e exige que 100% dos arquivos seja enviado. Ele foi projetado para acessar as capacidades de download em paralelo do HTTP/2, mas é um pequeno impulso que raramente irá compensar a sobrecarga do download de um aplicativo inteiro vs a quantidade mínima necessária.
  • 10 things I learned making the fastest website in the world por David Gilbertson estabelece outro padrão alto para otimização. Sem surpresa, o webpack desempenha um papel vital no alcance desses objetivos.
  • O foco deste artigo é o desempenho do front-end, não o tempo de compilação, mas ainda assim, há várias dicas sobre o assunto. Se você seguiu as dicas de compilação e está tendo problemas com uma compilação lenta com webpack –watch, tente usar o webpack-dev-server. Ele compila em memória e não escreve no sistema de arquivos, economizando preciosos segundos.