Este codelab explora como a minificação e a compactação do pacote JavaScript para o aplicativo a seguir melhoram a performance da página, reduzindo o tamanho da solicitação do app.
Medir
Antes de adicionar otimizações, é sempre recomendável analisar o estado atual do aplicativo.
- Para visualizar o site, pressione View App. Em seguida, pressione Fullscreen .
Esse app, que também foi abordado no codelab "Remover código não utilizado", permite que você vote no seu gatinho favorito. 🐈
Agora confira o tamanho desse aplicativo:
- Pressione "Control+Shift+J" (ou "Command+Option+J" no Mac) para abrir as Ferramentas do desenvolvedor.
- Clique na guia Rede.
- Marque a caixa de seleção Desativar cache.
- Atualize o app.
Embora tenha havido muito progresso no codelab "Remove unused code" para reduzir o tamanho do pacote, 225 KB ainda é bastante 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 do arquivo será de cerca de 112 B (bytes).
Se todos os espaços em branco forem removidos, o código resultante vai ficar assim:
function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}
O tamanho do arquivo agora é de cerca de 83 B. Se ele for ainda mais danificado 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 chega a 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 maneira. O benefício de ofuscar o código dessa maneira pode ajudar a alcançar tamanhos de arquivo menores. 112 bilhões não é muito para começar, mas ainda houve uma redução de 50% no tamanho.
Neste aplicativo, o webpack (link em inglês) versão 4 é usado como um
bundler de módulo. A versão específica pode ser encontrada 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 é o código minimizado, clique em
main.bundle.js
no painel Network do DevTools. Agora clique na guia
Resposta.
O código na forma final, minimizado e modificado, é mostrado no corpo da resposta.
Para descobrir o tamanho do pacote se ele não fosse minimizado, abra
webpack.config.js
e atualize a configuração mode
.
module.exports = {
mode: 'production',
mode: 'none',
//...
Atualize o aplicativo e confira o tamanho do pacote novamente no painel Network do DevTools.
Essa é uma diferença muito grande! 😅
Reverta as mudanças aqui antes de continuar.
module.exports = {
mode: 'production',
mode: 'none',
//...
A inclusão de um processo para minimizar o código no aplicativo depende das ferramentas usadas:
- 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. - Outros plug-ins de minificação também existem e podem ser usados, como o BabelMinifyWebpackPlugin e o ClosureCompilerPlugin.
- Se um bundler 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 vaga para explicar como o código é reduzido durante o processo de minificação, ele não é realmente comprimido no sentido literal.
A compressão geralmente se refere a um código modificado usando um algoritmo de compressã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, onde três tipos
são mostrados:
- Geral representa cabeçalhos gerais relevantes para toda a interação entre solicitação e resposta.
- Cabeçalhos de resposta mostra uma lista de cabeçalhos específicos para a resposta real do servidor.
- Cabeçalhos da solicitação mostra uma lista de cabeçalhos anexados à solicitação pelo cliente.
Observe o cabeçalho accept-encoding
no Request Headers
.
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 disponíveis, 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 de servidor e cliente. Ele é baseado no algoritmo Deflate e é compatível com todos os navegadores atuais. - Deflação (
deflate
): não é comumente usado. - 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 tem suporte nas versões mais recentes da maioria dos navegadores.
O aplicativo de exemplo neste tutorial é idêntico ao app concluído no codelab "Remove unused code" (remover código não utilizado), exceto pelo fato de que o Express agora é usado como um framework de servidor. Nas próximas seções, vamos abordar a compressão estática e dinâmica.
Compactação dinâmica
A compactação dinâmica envolve a compactação de recursos em tempo real conforme eles são solicitados pelo navegador.
Prós
- Não é necessário criar e atualizar versões compactadas salvas de recursos.
- A compactação on-the-fly 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 uma queda de desempenho, já que o usuário espera 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, isso importa express
e usa o middleware express.static
para carregar todos os arquivos estáticos de HTML, JS e CSS no
diretório public/
. Esses arquivos são criados pelo webpack em cada build.
Para garantir que todos os recursos sejam compactados sempre que forem solicitados, a
biblioteca de middleware de compressão pode
ser usada. Comece adicionando-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 como um middleware antes de montar o express.static
:
//...
const app = express();
app.use(compression());
app.use(express.static('public'));
//...
Agora, recarregue o app e confira o tamanho do pacote no painel Network.
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
.
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 a níveis altos de compactação não é mais um problema. Nada precisa acontecer em tempo real para compactar arquivos, já que eles podem ser buscados diretamente.
Contras
- Os recursos precisam ser compactados em cada build. Os tempos de build podem aumentar significativamente se níveis de compactação altos forem usados.
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.
O CompressionPlugin
pode ser usado para isso.
Comece adicionando-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 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 recriado, uma versão compactada do pacote principal é
criada. Abra o Glitch Console para conferir o que está dentro do
diretório public/
final que é servido pelo servidor Node.
- Clique no botão Ferramentas.
- Clique no botão Console.
- No console, execute os comandos abaixo para mudar para o diretório
public
e conferir todos os arquivos:
cd public
ls
A versão compactada do pacote, main.bundle.js.gz
, também é salva aqui. CompressionPlugin
também compacta index.html
por padrão.
A próxima coisa que precisa ser feita é informar ao servidor para enviar esses arquivos compactados
sempre que as versões originais do JS estiverem sendo 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 para um
endpoint específico. Uma função de callback é usada para definir como processar essa
solicitação. O trajeto funciona assim:
- Especificar
'*.js'
como o primeiro argumento significa que isso 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 respostaContent-Encoding
é definido comogzip
. - Por fim,
next()
garante que a sequência continue para qualquer callback que possa ser o próximo.
Quando o app for recarregado, confira o painel Network
novamente.
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 o padrão em muitas das ferramentas disponíveis atualmente. Por isso, é importante descobrir se a cadeia de ferramentas já oferece suporte a elas ou se você precisa começar a aplicar os dois processos.