Minifique e compacte payloads de rede com o gzip

Este codelab explora como minificar e compactar o pacote JavaScript para o aplicativo a seguir melhora o desempenho da página reduzindo o tamanho da solicitação do app.

Captura de tela do aplicativo

Medida

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

  • Para visualizar o site, pressione Ver app. Em seguida, pressione Tela cheia modo tela cheia.

Com ele, que também foi abordado no codelab Remover código não utilizado, você pode votar no seu gatinho favorito. 🐈

Confira o tamanho desse 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. Atualize o app.

Tamanho do pacote original no painel "Rede"

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

Minificação

Considere o bloco de código a seguir.

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 do arquivo será de cerca de 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 agora é de cerca de 83 B. Se ele ficar ainda mais corrompido ao reduzir o comprimento do nome da variável e modificar 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 atinge 62 B.

A cada etapa, a leitura do código fica mais difícil. 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 conseguir tamanhos de arquivo menores. 112B realmente não era muito para começar, mas ainda havia uma redução de 50% no tamanho.

Neste aplicativo, a versão 4 do webpack é usada como um bundler de módulo. A versão específica está disponível em package.json.

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

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

Para ter uma ideia da aparência do código minimizado, clique em main.bundle.js ainda no painel Network do DevTools. Agora clique na guia Resposta.

Resposta reduzida

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

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

Atualize o app e confira o tamanho do pacote novamente no painel Rede do DevTools.

Tamanho do pacote de 767 KB

É uma diferença bem grande! 😅

Reverta as mudanças antes de continuar.

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

A inclusão de um processo para reduzir o código no aplicativo depende das ferramentas que você usa:

  • Se o webpack v4 ou versão mais recente for usado, nenhum trabalho extra precisará ser feito, 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.
  • Outros plug-ins de minificação também existem e podem ser usados, como BabelMinifyWebpackPlugin e ClosureCompilerPlugin (links em inglês).
  • Se um bundler de módulo 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 "compactação" às vezes seja usado vagamente para explicar como o código é reduzido durante o processo de minificação, ele não é 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.

A cada solicitação e resposta HTTP, navegadores e servidores da Web podem adicionar headers para incluir mais informações sobre o recurso que está sendo buscado ou recebido. Isso pode ser consultado na guia Headers do painel "Rede" do DevTools, em que três tipos são mostrados:

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

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

Aceitar cabeçalho de codificação

A accept-encoding é usada pelo navegador para especificar os formatos de codificação de conteúdo ou algoritmos de compactação compatíveis. Existem 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 servidores e clientes. Ela se baseia 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 melhora ainda mais as taxas de compactação, o que pode resultar em carregamentos de página ainda mais rápidos. Ela é 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 utilizado, exceto pelo fato de que o Express agora é usado como um framework do 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 dinâmica de recursos à medida que eles são solicitados pelo navegador.

Prós

  • Não é preciso criar e atualizar versões compactadas salvas de recursos.
  • A compactação dinâmica funciona especialmente para páginas da Web que são geradas dinamicamente.

Desvantagens

  • Compactar arquivos em níveis mais altos para conseguir taxas de compactação melhores leva mais tempo. Isso pode causar um impacto no desempenho, já que o usuário aguarda a compactação dos recursos 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 o que isso faz é importar express e usar 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, a biblioteca de middleware de compactação pode ser usada. Para começar, adicione-o como um devDependency em package.json:

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

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

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

E adicione-o como um middleware antes de express.static ser ativado:

//...

const app = express();

app.use(compression());

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

//...

Atualize o app e confira o tamanho do pacote no painel Rede.

Tamanho do pacote com a compactação dinâmica

De 225 KB a 61,6 KB! Agora 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 os recursos compactados e salvos com antecedência.

Prós

  • A latência devido aos altos níveis de compactação não é mais uma preocupação. Nada precisa acontecer imediatamente para compactar arquivos, já que agora eles podem ser buscados diretamente.

Desvantagens

  • Os recursos precisam ser compactados em cada build. Os tempos de compilação podem aumentar significativamente se níveis de compactação altos forem usados.

Compactação estática com Node/Express e webpack

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

Para começar, adicione-o 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-o na matriz plugins:

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

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

Quando o app é recarregado e recriado, uma versão compactada do pacote principal é criada. Abra o Console do Glitch para ver o que está dentro do diretório public/ final que é exibido pelo servidor do nó.

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

Arquivos finais de saída no diretório público

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

A próxima coisa que precisa ser feita é pedir ao servidor para enviar esses arquivos compactados em gzip sempre que as versões JS originais delas forem solicitadas. Isso pode ser feito definindo uma nova rota no server.js antes que os arquivos sejam disponibilizados 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 para um endpoint específico. Uma função de callback é usada para definir como lidar com essa solicitação. O trajeto funciona assim:

  • Especificar '*.js' como o primeiro argumento significa que ele funciona para todos os endpoints acionados 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 atualizado, confira o painel Network mais uma vez.

Redução do tamanho do pacote com a compactação estática

Como antes, houve uma redução significativa no tamanho do pacote.

Conclusão

Este codelab abordou o processo de minificação e compactação do código-fonte. Essas duas técnicas estão se tornando padrão em muitas das ferramentas disponíveis atualmente, por isso é importante descobrir se seu conjunto de ferramentas já é compatível com elas ou se você precisa começar a aplicar os dois processos.