Minifique e compacte payloads de rede com o gzip

Este codelab mostra como minimizar e compactar o pacote JavaScript para o aplicativo a seguir, melhorando o desempenho da página ao reduzir o tamanho da solicitação do app.

Captura de tela do aplicativo

Medir

Antes de adicionar otimizações, é sempre bom analisar o estado atual do aplicativo.

  • Para visualizar o site, pressione Ver app e depois Tela cheia tela cheia.

Esse app, que também foi abordado no codelab "Remover código não utilizado", permite que você vote no seu gatinho favorito. 🐈

Agora veja o tamanho do aplicativo:

  1. Pressione "Control+Shift+J" (ou "Command+Option+J" no Mac) para abrir o DevTools.
  2. Clique na guia Rede.
  3. Marque a caixa de seleção Desativar cache.
  4. Recarregue o app.

Tamanho do pacote original no painel de rede

Embora tenha havido muito progresso no codelab "Remover código não utilizado" para reduzir o tamanho do pacote, 225 KB ainda é muito grande.

Minificação

Considere o seguinte bloco de código.

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

Se essa função for salva em um arquivo próprio, o tamanho dele será de aproximadamente 112 B (bytes).

Se todos os espaços em branco forem removidos, o código resultante será parecido com este:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

O tamanho do arquivo seria de cerca de 83 B. Se ele for ainda mais corrompido pela redução do comprimento do nome da variável e pela modificação de algumas expressões, o código final poderá ficar assim:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

O tamanho do arquivo agora é de 62 B.

A cada etapa, o código fica mais difícil de ler. No entanto, o mecanismo JavaScript do navegador interpreta cada um deles da mesma forma. O benefício de ofuscar o código dessa maneira pode ajudar a reduzir o tamanho dos arquivos. 112 B não era muito para começar, mas ainda houve uma redução de 50% no tamanho.

Neste aplicativo, a versão 4 do webpack é usada como um pacote de módulo. A versão específica pode ser vista em package.json.

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

A versão 4 já minimiza o pacote por padrão durante o modo de produção. Ele usa TerserWebpackPlugin um plug-in para Terser. O Terser é uma ferramenta conhecida usada para compactar código JavaScript.

Para ter uma ideia de como fica o código minimizado, clique em main.bundle.js enquanto ainda estiver no painel Rede do DevTools. Agora clique na guia Resposta.

Resposta reduzida

O código na forma final, minificado e corrompido, é mostrado no corpo da resposta. Para saber o tamanho do pacote se ele não tivesse sido minimizado, abra webpack.config.js e atualize a configuração mode.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Recarregue o aplicativo e confira o tamanho do pacote novamente no painel Rede das DevTools.

Tamanho do pacote de 767 KB

Essa é uma diferença bem grande! 😅

Reverta as mudanças aqui antes de continuar.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Incluir um processo para reduzir o código no aplicativo depende das ferramentas que você usa:

  • Se o webpack v4 ou mais recente for usado, não será necessário fazer mais nada, já que o código é minimizado por padrão no modo de produção. 👍
  • Se uma versão mais antiga do webpack for usada, instale e inclua TerserWebpackPlugin no processo de build do webpack. A documentação explica isso em detalhes.
  • Existem outros plug-ins de minificação que podem ser usados, como BabelMinifyWebpackPlugin e ClosureCompilerPlugin.
  • Se um pacote de módulos não estiver sendo usado, use o Terser como uma ferramenta de CLI ou inclua-o diretamente como uma dependência.

Compactação

Embora o termo "compressão" às vezes seja usado de forma imprecisa para explicar como o código é reduzido durante o processo de minificação, ele não é realmente compactado no sentido literal.

Compactação geralmente se refere a um código que foi modificado usando um algoritmo de compactação de dados. Ao contrário da minificação, que acaba fornecendo um código perfeitamente válido, o código compactado precisa ser descompactado antes de ser usado.

Com cada solicitação e resposta HTTP, os navegadores e servidores da Web podem adicionar cabeçalhos para incluir informações adicionais sobre o recurso que está sendo buscado ou recebido. Isso pode ser visto na guia Headers do painel de rede do DevTools, em que três tipos são mostrados:

  • General representa cabeçalhos gerais relevantes para toda a interação de solicitação-resposta.
  • Cabeçalhos de resposta mostra uma lista de cabeçalhos específicos da resposta real do servidor.
  • Cabeçalhos da solicitação mostra uma lista de cabeçalhos anexados à solicitação pelo cliente.

Confira o cabeçalho accept-encoding no Request Headers.

Cabeçalho de codificação Accept

O accept-encoding é usado pelo navegador para especificar quais formatos de codificação de conteúdo ou algoritmos de compactação ele aceita. Há muitos algoritmos de compactação de texto, mas apenas três são compatíveis com a compactação (e descompactação) de solicitações de rede HTTP:

  • Gzip (gzip): o formato de compactação mais usado para interações entre servidor e cliente. Ele é criado com base no algoritmo Deflate e é compatível com todos os navegadores atuais.
  • Deflate (deflate): não é usado com frequência.
  • Brotli (br): um algoritmo de compactação mais recente que visa melhorar ainda mais as taxas de compactação, o que pode resultar em carregamentos de página ainda mais rápidos. Ele é compatível com as versões mais recentes da maioria dos navegadores.

O aplicativo de exemplo neste tutorial é idêntico ao app concluído no codelab "Remover código não usado", exceto pelo fato de que o Express agora é usado como um framework de servidor. Nas próximas seções, vamos abordar a compactação estática e dinâmica.

Compactação dinâmica

A compactação dinâmica envolve a compactação de recursos on-the-fly à medida que são solicitados pelo navegador.

Prós

  • Não é necessário criar e atualizar versões compactadas salvas de recursos.
  • A compactação dinâmica funciona muito bem para páginas da Web geradas dinamicamente.

Contras

  • A compactação de arquivos em níveis mais altos para alcançar melhores taxas de compactação leva mais tempo. Isso pode causar um impacto no desempenho, já que o usuário precisa esperar que os recursos sejam compactados antes de serem enviados pelo servidor.

Compactação dinâmica com Node/Express

O arquivo server.js é responsável por configurar o servidor Node que hospeda o aplicativo.

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

No momento, tudo isso importa express e usa o middleware express.static para carregar todos os arquivos estáticos HTML, JS e CSS no diretório public/. Esses arquivos são criados pelo webpack a cada build.

Para garantir que todos os recursos sejam compactados sempre que forem solicitados, use a biblioteca de middleware compression. Comece adicionando como um devDependency em package.json:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

E importe para o arquivo do servidor, server.js:

const express = require('express');
const compression = require('compression');

Adicione-o como um middleware antes de express.static ser montado:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

Agora, recarregue o app e confira o tamanho do pacote no painel Rede.

Tamanho do pacote com compactação dinâmica

De 225 KB para 61,6 KB! No Response Headers, um cabeçalho content-encoding mostra que o servidor está enviando esse arquivo codificado com gzip.

Cabeçalho de codificação de conteúdo

Compactação estática

A ideia por trás da compactação estática é ter recursos compactados e salvos com antecedência.

Prós

  • A latência devido a altos níveis de compactação não é mais um problema. Não é necessário fazer nada na hora para compactar arquivos, já que eles podem ser buscados diretamente.

Contras

  • Os recursos precisam ser compactados a cada build. Os tempos de build podem aumentar significativamente se forem usados níveis de compressão altos.

Compressão estática com Node/Express e webpack

Como a compactação estática envolve a compactação de arquivos com antecedência, as configurações do webpack podem ser modificadas para compactar recursos como parte da etapa de build. CompressionPlugin pode ser usado para isso.

Comece adicionando como um devDependency em package.json:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

Como qualquer outro plug-in do webpack, importe-o no arquivo de configurações, webpack.config.js:

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

e inclua na matriz plugins:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

Por padrão, o plug-in compacta os arquivos de build usando gzip. Consulte a documentação para saber como adicionar opções para usar um algoritmo diferente ou incluir/excluir determinados arquivos.

Quando o app é recarregado e recompilado, uma versão compactada do pacote principal é criada. Abra o console do Glitch para conferir o conteúdo do diretório public/ final veiculado pelo servidor Node.

  • Clique no botão Ferramentas.
  • Clique no botão Console.
  • No console, execute os seguintes comandos para mudar para o diretório public e conferir todos os arquivos dele:
cd public
ls

Arquivos finais gerados no diretório público

A versão compactada com gzip do pacote, main.bundle.js.gz, também está salva aqui. CompressionPlugin também compacta index.html por padrão.

Em seguida, é necessário informar ao servidor para enviar esses arquivos compactados sempre que as versões originais em JS forem solicitadas. Isso pode ser feito definindo uma nova rota em server.js antes que os arquivos sejam veiculados com express.static.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

app.get é usado para informar ao servidor como responder a uma solicitação GET de um endpoint específico. Uma função de callback é usada para definir como processar essa solicitação. A rota funciona assim:

  • Especificar '*.js' como o primeiro argumento significa que isso funciona para todos os endpoints disparados para buscar um arquivo JS.
  • No callback, .gz é anexado ao URL da solicitação, e o cabeçalho de resposta Content-Encoding é definido como gzip.
  • Por fim, next() garante que a sequência continue para qualquer callback que possa ser o próximo.

Depois que o app for recarregado, confira o painel Network mais uma vez.

Redução do tamanho do pacote com compressão estática

Assim como antes, uma redução significativa no tamanho do pacote.

Conclusão

Este codelab abordou o processo de redução e compactação do código-fonte. Essas duas técnicas estão se tornando padrão em muitas das ferramentas disponíveis hoje. Por isso, é importante descobrir se sua cadeia de ferramentas já as oferece suporte ou se você precisa começar a aplicar os dois processos por conta própria.