Como o webpack ajuda no armazenamento em cache de recursos
A próxima etapa (depois de otimizar o tamanho do app) que melhora o tempo de carregamento do app é o armazenamento em cache. Use para manter partes do app no e evite sempre fazer o download deles novamente.
Usar o controle de versões de pacotes e os cabeçalhos de cache
A abordagem comum de fazer o armazenamento em cache é:
pedir ao navegador para armazenar um arquivo em cache por um período muito longo (por exemplo, um ano):
# Server header Cache-Control: max-age=31536000
Se você não sabe o que o
Cache-Control
faz, consulte o livro (em inglês) de Jake Archibald excelente postagem sobre práticas recomendadas práticas recomendadas.e renomeie o arquivo quando ele for alterado para forçar o novo download:
<!-- Before the change --> <script src="./index-v15.js"></script> <!-- After the change --> <script src="./index-v16.js"></script>
Esse método instrui o navegador a baixar o arquivo JS, armazená-lo em cache e usar a cópia em cache. O navegador só acessará a rede se o nome do arquivo for alterado (ou se passar um ano).
Com o webpack, você faz o mesmo, mas em vez de um número de versão, você especifica a
hash do arquivo. Para incluir o hash no nome do arquivo, use
[chunkhash]
:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
}
};
Se você precisar da
para enviá-lo ao cliente, use o HtmlWebpackPlugin
ou o
WebpackManifestPlugin
.
O HtmlWebpackPlugin
é uma
abordagem simples, mas menos flexível. Durante a compilação, o plug-in gera uma
Arquivo HTML que inclui todos os recursos compilados. Se a lógica do servidor não estiver
complexa, ela será suficiente para você:
<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
O
WebpackManifestPlugin
é uma abordagem mais flexível, útil se você tiver uma parte complexa do servidor.
Durante o build, ele gera um arquivo JSON com um mapeamento entre os nomes dos arquivos
sem hash e nomes de arquivo com hash. Use este JSON no servidor para descobrir
com qual arquivo trabalhar:
// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}
Leitura adicional
Extrair dependências e ambiente de execução em um arquivo separado
Dependências
As dependências de apps tendem a mudar com menos frequência do que o código real do app. Ao se mover em um arquivo separado, o navegador poderá armazená-los em cache separadamente – e não fará o download novamente sempre que o código do app mudar.
Para extrair dependências em um bloco separado, execute três etapas:
Substitua o nome do arquivo de saída por
[name].[chunkname].js
:// webpack.config.js module.exports = { output: { // Before filename: 'bundle.[chunkhash].js', // After filename: '[name].[chunkhash].js' } };
Quando o webpack cria o app, ele substitui
[name]
. com o nome de um bloco. Se não adicionarmos a parte[name]
, teremos para diferenciar as partes pelo hash delas, o que é bem difícil.Converta o campo
entry
em um objeto:// webpack.config.js module.exports = { // Before entry: './index.js', // After entry: { main: './index.js' } };
Neste snippet, "main" é o nome de um bloco. Esse nome será substituído em lugar de
[name]
da etapa 1.Até agora, se você criar o aplicativo, esse bloco incluirá todo o código do aplicativo, apenas como se não tivéssemos concluído essas etapas. Mas isso vai mudar em alguns segundos.
No webpack 4, adicione a opção
optimization.splitChunks.chunks: 'all'
à configuração do webpack:// webpack.config.js (for webpack 4) module.exports = { optimization: { splitChunks: { chunks: 'all' } } };
Essa opção ativa a divisão inteligente de código. Com ele, o webpack extrairia o código do fornecedor se ele chega a mais de 30 KB (antes da minificação e do gzip). Ele também extrairia o código comum, isso é útil se o build produz vários pacotes (por exemplo, se você dividir seu app em rotas).
No webpack 3, adicione o
CommonsChunkPlugin
:// webpack.config.js (for webpack 3) module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ // A name of the chunk that will include the dependencies. // This name is substituted in place of [name] from step 1 name: 'vendor', // A function that determines which modules to include into this chunk minChunks: module => module.context && module.context.includes('node_modules'), }) ] };
Esse plug-in usa todos os módulos que incluem
node_modules
e caminhos move os arquivos para um arquivo separado chamadovendor.[chunkhash].js
.
Após essas mudanças, cada build vai gerar dois arquivos em vez de um: main.[chunkhash].js
e
vendor.[chunkhash].js
(vendors~main.[chunkhash].js
para o webpack 4). No caso do webpack 4,
o pacote de fornecedores poderá não ser gerado se as dependências forem pequenas. E tudo bem:
$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
Asset Size Chunks Chunk Names
./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main
./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor
O navegador armazenaria esses arquivos em cache separadamente e faria o download novamente apenas do código alterado.
Código do ambiente de execução do Webpack
Infelizmente, não basta extrair apenas o código do fornecedor. Se você tentar mudar algo no código do app:
// index.js
…
…
// E.g. add this:
console.log('Wat');
você perceberá que o hash vendor
também muda:
Asset Size Chunks Chunk Names
./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor
↓
Asset Size Chunks Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js 47 kB 1 [emitted] vendor
Isso acontece porque o pacote webpack, além do código dos módulos, tem um ambiente de execução: um pequeno trecho de código que gerencia a execução do módulo. Ao dividir o código em vários arquivos, este código começa a incluir um mapeamento entre IDs de blocos e arquivos correspondentes:
// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
"0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";
O Webpack inclui esse ambiente de execução no último bloco gerado, que é vendor
no nosso caso. E toda vez que qualquer bloco muda, esse trecho de código também muda,
fazendo com que todo o bloco vendor
mude.
Para resolver isso, vamos mover o ambiente de execução para um arquivo separado. No webpack 4, isso é
ativando a opção optimization.runtimeChunk
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};
No webpack 3,faça isso criando um bloco vazio extra com o CommonsChunkPlugin
:
// webpack.config.js (for webpack 3)
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => module.context && module.context.includes('node_modules')
}),
// This plugin must come after the vendor one (because webpack
// includes runtime into the last chunk)
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
// minChunks: Infinity means that no app modules
// will be included into this chunk
minChunks: Infinity
})
]
};
Após essas mudanças, cada build vai gerar três arquivos:
$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
Asset Size Chunks Chunk Names
./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 1 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
Inclua-os em index.html
na ordem inversa. Pronto:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>
Leitura adicional
- Guia do webpack sobre armazenamento em cache de longo prazo
- Documentos do Webpack sobre o tempo de execução do webpack e manifesto
- "Aproveitar ao máximo CommonsChunkPlugin"
- Como
optimization.splitChunks
eoptimization.runtimeChunk
funcionam
Ambiente de execução do webpack inline para economizar uma solicitação HTTP extra
Para melhorar ainda mais, tente in-line o tempo de execução do webpack no código HTML resposta. Ou seja, em vez de:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
faça isto:
<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>
O ambiente de execução é pequeno e in-line ajuda a economizar uma solicitação HTTP importante com HTTP/1. menos importante com o HTTP/2, mas ainda pode ter ).
É muito fácil:
Se você gerar HTML com o plug-in HTMLWebpackPlugin
Se você usar o método HtmlWebpackPlugin para gerar um arquivo HTML, o InlineSourcePlugin é tudo de que você precisa:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
inlineSource: 'runtime~.+\\.js',
}),
new InlineSourcePlugin()
]
};
Se você gerar HTML usando uma lógica de servidor personalizada
Com o webpack 4:
Adicione o método
WebpackManifestPlugin
para saber o nome gerado do bloco do ambiente de execução:// webpack.config.js (for webpack 4) const ManifestPlugin = require('webpack-manifest-plugin'); module.exports = { plugins: [ new ManifestPlugin() ] };
Um build com esse plug-in criaria um arquivo parecido com este:
// manifest.json { "runtime~main.js": "runtime~main.8e0d62a03.js" }
Inline o conteúdo da parte do tempo de execução de maneira conveniente. Por exemplo: com Node.js e Express:
// server.js const fs = require('fs'); const manifest = require('./manifest.json'); const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
Ou com o webpack 3:
Torne o nome do ambiente de execução estático especificando
filename
:module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, filename: 'runtime.js' }) ] };
In-line o conteúdo do
runtime.js
de maneira conveniente. Por exemplo: com Node.js e Express:// server.js const fs = require('fs'); const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
Carregar lentamente o código que você não precisa no momento
Às vezes, uma página tem mais e menos partes importantes:
- Se você carregar uma página de vídeo no YouTube, vai se importar mais com o vídeo do que com comentários. Aqui, o vídeo é mais importante que os comentários.
- Ao abrir um artigo em um site de notícias, você se importa mais com o texto do do que sobre anúncios. Aqui, o texto é mais importante que os anúncios.
Nesses casos, melhore o desempenho do carregamento inicial fazendo o download apenas do
as partes mais importantes primeiro e
o carregamento lento das partes restantes depois. Use o
função import()
e
code-splitting para isso:
// videoPlayer.js
export function renderVideoPlayer() { … }
// comments.js
export function renderComments() { … }
// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();
// …Custom event listener
onShowCommentsClick(() => {
import('./comments').then((comments) => {
comments.renderComments();
});
});
import()
especifica que você quer carregar um módulo específico dinamicamente. Quando
o webpack detectar o import('./module.js')
, ele moverá esse módulo para um
chunk:
$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./main.f7e53d8e13e9a2745d6d.js 60 kB 1 [emitted] main
./vendor.4f14b6326a80f4752a98.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
e faz o download somente quando a execução atinge a função import()
.
Isso vai reduzir o pacote main
, melhorando o tempo de carregamento inicial.
Isso melhora ainda mais o armazenamento em cache. Se você alterar o código na parte principal,
bloco de comentários não será afetado.
Leitura adicional
- Documentos do Webpack para o
import()
função - A proposta do JavaScript para implementar o
import()
sintaxe
Dividir o código em rotas e páginas
Se o app tiver várias rotas ou páginas, mas houver apenas um arquivo JS com
(um único bloco de main
), é provável que você esteja disponibilizando bytes extras em
cada solicitação. Por exemplo, quando um usuário visita a página inicial do seu site:
eles não precisam carregar o código para renderizar um artigo que está em uma página, mas eles a carregarão. Além disso, se o usuário sempre visita apenas a casa página e fizer uma alteração no código do artigo, o webpack vai invalidar o o pacote inteiro, e o usuário terá que fazer o download de todo o aplicativo novamente.
Se dividirmos o aplicativo em páginas (ou rotas, no caso de um aplicativo de página única), o usuário fará o download apenas do código relevante. Além disso, o navegador armazenará o código do app em cache melhor: se você alterar o código da página inicial, o webpack invalidará apenas o no bloco correspondente.
Para apps de página única
Para dividir apps de página única por rotas, use import()
. Consulte a página
que você não precisa no momento”). Se você usar uma estrutura,
ele pode já ter uma solução para isso:
- "Código
Divisão"
nos documentos de
react-router
(para React) - "Carregamento lento
Routes" em
Documentos de
vue-router
(para Vue.js)
Para aplicativos tradicionais de várias páginas
Para dividir apps tradicionais por páginas, use a entrada do webpack pontos. Se o app tem três tipos de página: a página inicial, a página do artigo e a página da conta de usuário, deve ter três entradas:
// webpack.config.js
module.exports = {
entry: {
home: './src/Home/index.js',
article: './src/Article/index.js',
profile: './src/Profile/index.js'
}
};
Para cada arquivo de entrada, o webpack vai criar uma árvore de dependências separada e gerar um pacote que inclui apenas módulos usados por essa entrada:
$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./home.91b9ed27366fe7e33d6a.js 18 kB 1 [emitted] home
./article.87a128755b16ac3294fd.js 32 kB 2 [emitted] article
./profile.de945dc02685f6166781.js 24 kB 3 [emitted] profile
./vendor.4f14b6326a80f4752a98.js 46 kB 4 [emitted] vendor
./runtime.318d7b8490a7382bf23b.js 1.45 kB 5 [emitted] runtime
Portanto, se apenas a página do artigo usar o Lodash, os pacotes home
e profile
não inclui o arquivo, e o usuário não precisa fazer o download da biblioteca quando
acessando a página inicial.
Árvores de dependência separadas têm desvantagens. Se dois pontos de entrada usarem
Lodash, e você não moveu suas dependências para um pacote de fornecedor, tanto a entrada
incluindo uma cópia do Lodash. Para resolver isso, no webpack 4,adicione o
optimization.splitChunks.chunks: 'all'
na configuração do webpack:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Essa opção ativa a divisão inteligente de código. Com essa opção, o webpack automaticamente procure códigos comuns e os extraia em arquivos separados.
Ou, no webpack 3, use o CommonsChunkPlugin
ele moverá dependências comuns para um novo arquivo especificado:
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2 // 2 is the default value
})
]
};
Fique à vontade para testar o valor minChunks
para encontrar o melhor. Geralmente,
você quer mantê-lo pequeno, mas aumentar se o número de blocos crescer. Para
exemplo, para 3 blocos, minChunks
pode ser 2, mas para 30 blocos, pode ser 8.
porque se você mantiver em 2, muitos módulos entrarão no arquivo comum,
inflando demais.
Leitura adicional
- Documentos do Webpack sobre o conceito de entrada pontos
- Documentos do Webpack sobre a CommonsChunkPlugin
- "Aproveitar ao máximo CommonsChunkPlugin"
- Como
optimization.splitChunks
eoptimization.runtimeChunk
funcionam
Tornar os IDs dos módulos mais estáveis
Ao criar o código, o webpack atribui um ID a cada módulo. Depois, esses IDs são
usada em require()
s dentro do pacote. Normalmente, os IDs são mostrados na saída do build
logo antes dos caminhos do módulo:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./main.4e50a16675574df6a9e9.js 60 kB 1 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
↓ Aqui
[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Por padrão, os IDs são calculados usando um contador (ou seja, o primeiro módulo tem o ID 0, o segundo tem ID 1 e assim por diante). O problema é que, quando você adiciona um novo módulo, ele pode aparecer no meio da lista de módulos, alterando todos os próximos módulos" IDs:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.5c82c0f337fcb22672b5.js 22 kB 0 [emitted]
./main.0c8b617dfc40c2827ae3.js 82 kB 1 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
↓ Adicionamos um novo módulo...
[4] ./webPlayer.js 24 kB {1} [built]
↓ Veja o que ele fez! comments.js
agora tem o ID 5 em vez de 4
[5] ./comments.js 58 kB {0} [built]
↓ ads.js
agora tem o ID 6 em vez de 5
[6] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Isso invalida todos os blocos que incluem ou dependem de módulos com IDs alterados –
mesmo que o código real não tenha sido alterado. No nosso caso, o bloco 0
(o bloco
com comments.js
) e o bloco main
(o bloco com o outro código do app) recebem
invalidado, enquanto apenas o main
deveria ter sido.
Para resolver isso, mude como os IDs dos módulos são calculados usando a função
HashedModuleIdsPlugin
Ele substitui IDs baseados em contagem por hashes de caminhos do módulo:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.6168aaac8461862eab7a.js 22.5 kB 0 [emitted]
./main.a2e49a279552980e3b91.js 60 kB 1 [emitted] main
./vendor.ff9f7ea865884e6a84c8.js 46 kB 2 [emitted] vendor
./runtime.25f5d0204e4f77fa57a1.js 1.45 kB 3 [emitted] runtime
↓ Aqui
[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Nessa abordagem, o ID de um módulo só muda se você renomear ou mover esse mais tarde neste módulo. Os novos módulos não afetarão outros módulos do Google Ads.
Para ativar o plug-in, adicione-o à seção plugins
da configuração:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};
Leitura adicional
- Documentos do Webpack sobre a HashedModuleIdsPlugin
Resumo
- Armazene o pacote em cache e diferencie as versões alterando o nome do pacote
- Dividir o pacote em código do app, código do fornecedor e ambiente de execução
- In-line o ambiente de execução para salvar uma solicitação HTTP
- Carregar código não crítico lentamente com
import
- Divida o código por rotas/páginas para evitar o carregamento de itens desnecessários