Uma nova estratégia de divisão de webpack no Next.js e no Gatsby minimiza o código duplicado para melhorar o desempenho do carregamento da página.
O Chrome está colaborando com ferramentas e no ecossistema de código aberto JavaScript. Várias otimizações mais recentes foram feitas recentemente foi adicionado para melhorar o desempenho de carregamento da Next.js e Gatsby. Este artigo aborda uma estratégia aprimorada de divisão granular que agora é enviado por padrão nos dois frameworks.
Introdução
Como muitos frameworks da Web, o Next.js e o Gatsby usam o webpack como núcleo
bundler. O webpack v3 foi lançado
CommonsChunkPlugin
para que seja possível
módulos de saída compartilhados entre diferentes pontos de entrada em um ou poucos "comuns" bloco (ou
blocos). Códigos compartilhados podem ser baixados separadamente e armazenados no cache do navegador antecipadamente, o que pode
resultam em um melhor desempenho de carregamento.
Esse padrão se tornou popular com muitas estruturas de aplicativos de página única adotando um ponto de entrada e de pacote com esta aparência:
Embora seja prático, o conceito de agrupar todo o código do módulo compartilhado em um único bloco tem sua
limitações. É possível fazer o download de módulos não compartilhados em todos os pontos de entrada para trajetos que não os utilizam.
resultando em downloads de mais códigos do que o necessário. Por exemplo, quando page1
é carregado
no bloco common
, ele carrega o código para moduleC
mesmo que page1
não use moduleC
.
Por esse motivo, junto com alguns outros, o webpack v4 removeu o plug-in em favor de um novo
um: SplitChunksPlugin
.
Divisão aprimorada
As configurações padrão de SplitChunksPlugin
funcionam bem para a maioria dos usuários. Vários blocos divididos são
criada de acordo com uma série de condições
para evitar a busca de código duplicado em várias rotas.
No entanto, muitos frameworks da Web que usam esse plug-in ainda seguem um modelo "único comum" abordagem de bloco
divisão. O Next.js, por exemplo, geraria um pacote commons
que continha qualquer módulo
usadas em mais de 50% das páginas e em todas as dependências de framework (react
, react-dom
e assim por diante).
const splitChunksConfigs = {
…
prod: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
commons: {
name: 'commons',
chunks: 'all',
minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
},
react: {
name: 'commons',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
},
},
},
Embora incluir código dependente de framework em um bloco compartilhado significa que ele pode ser baixado e armazenados em cache para qualquer ponto de entrada, a heurística baseada em uso de incluir módulos comuns usados em mais de metade das páginas não é muito eficaz. A modificação dessa proporção só resultaria em um destes dois resultados:
- Se você reduzir a proporção, será feito o download de mais códigos desnecessários.
- Se você aumentar a proporção, mais códigos vão ser duplicados em vários trajetos.
Para resolver esse problema, a Next.js adotou uma opção diferente
padrão paraSplitChunksPlugin
que reduz
código desnecessário para qualquer trajeto.
- Qualquer módulo de terceiros grande o suficiente (maior que 160 KB) é dividido em módulos individuais parte
- Um bloco
frameworks
separado é criado para as dependências do framework (react
,react-dom
e assim por diante) - Quantos blocos compartilhados forem criados, conforme necessário (até 25)
- O tamanho mínimo para que um bloco seja gerado é alterado para 20 KB
Essa estratégia de divisão granular oferece os seguintes benefícios:
- Melhorias no tempo de carregamento da página. Emitindo vários blocos compartilhados, em vez de apenas um, minimiza a quantidade de código desnecessário (ou duplicado) para qualquer ponto de entrada.
- Armazenamento em cache aprimorado durante as navegações. Dividir bibliotecas grandes e dependências de framework em blocos separados, o que reduz a possibilidade de invalidação do cache, pois é improvável que ambos mudança até que um upgrade seja feito.
Você pode consultar toda a configuração que o Next.js adotou no webpack-config.ts
.
Mais solicitações HTTP
SplitChunksPlugin
definiu a base para a divisão granular e aplica essa abordagem a uma
como Next.js não era um conceito totalmente novo. No entanto, muitas estruturas continuaram
usam uma única heurística e "comuns" estratégia de pacotes por alguns motivos. Isso inclui a preocupação de que
muito mais solicitações HTTP podem afetar negativamente o desempenho do site.
Os navegadores só podem abrir um número limitado de conexões TCP para uma única origem (seis para o Chrome). Portanto, minimizar o número de blocos gerados por um bundler pode garantir que o número total de solicitações permanece abaixo desse limite. No entanto, isso vale apenas para HTTP/1.1. Multiplexação em HTTP/2 permite que várias solicitações sejam transmitidas em paralelo usando uma única conexão em uma única origem. Em outras palavras, geralmente não precisamos nos preocupar em limitar o número de blocos emitido pelo nosso bundler.
Todos os principais navegadores são compatíveis com HTTP/2. Equipes do Chrome e Next.js
queria saber se aumentaria o número de solicitações dividindo os "comuns" únicos do Next.js pacote
em vários blocos compartilhados afetaria de alguma forma o desempenho do carregamento. Eles começaram medindo
desempenho de um único site enquanto modifica o número máximo de solicitações paralelas usando o método
maxInitialRequests
.
Em uma média de três execuções de vários testes em uma única página da Web, o
load
,
start-render
e First Contentful Paint permaneceram iguais ao variar o valor inicial máximo
contagem de solicitações (de 5 a 15). Curiosamente, notamos uma leve sobrecarga no desempenho,
depois de dividir agressivamente centenas de solicitações.
Isso mostrou que ficar abaixo de um limite confiável (20 a 25 solicitações) era o equilíbrio certo.
entre o desempenho de carregamento
e a eficiência do armazenamento em cache. Após alguns testes de referência, 25 foram
a contagem de maxInitialRequest
.
Modificar o número máximo de solicitações que ocorrem em paralelo resultou em mais de uma em um pacote compartilhado, e a separação em cada ponto de entrada reduziu significativamente de código desnecessário para a mesma página.
O objetivo do experimento era modificar o número de solicitações para verificar se havia alguma
impacto negativo no desempenho do carregamento da página. Os resultados sugerem que definir maxInitialRequests
como
25
na página de teste era ideal porque reduziu o tamanho do payload do JavaScript sem desacelerar
para baixo na página. A quantidade total de JavaScript necessária para hidratar a página ainda permaneceu
Isso explica por que o desempenho do carregamento da página não melhorou necessariamente com a redução
de código.
O webpack usa 30 KB como o tamanho mínimo padrão para que um bloco seja gerado. No entanto, o acoplamento de uma
O valor de maxInitialRequests
de 25 com um tamanho mínimo de 20 KB resultou em melhor armazenamento em cache.
Reduções de tamanho com blocos granulares
Muitos frameworks, incluindo o Next.js, dependem do roteamento do lado do cliente (gerenciado pelo JavaScript) para injetar tags de script mais recentes para cada transição de rota. Mas como eles predeterminam esses blocos dinâmicos no tempo de build?
Next.js usa um arquivo de manifesto de build do lado do servidor para determinar quais blocos gerados são usados diferentes pontos de entrada. Para fornecer essas informações ao cliente também, um resumo do lado do cliente arquivo de manifesto do build foi criado para mapear todas as dependências para cada ponto de entrada.
// Returns a promise for the dependencies for a particular route
getDependencies (route) {
return this.promisedBuildManifest.then(
man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
)
}
Essa nova estratégia de divisão granular foi lançada primeiro no Next.js atrás de uma flag, onde foi testada em um de usuários iniciais. Muitos notaram reduções significativas no total de JavaScript usado em seus site inteiro:
Site | Alteração total de JS | Diferença % |
---|---|---|
https://www.barnebys.com/ | -238 KB | 23% |
https://sumup.com/ | -220 KB | 30% |
https://www.hashicorp.com/ | 11 MB | 71% |
A versão final foi enviada por padrão na versão 9.2.
Gatsby
O Gatsby costumava seguir a mesma abordagem de uma heurística para definir módulos comuns:
config.optimization = {
…
splitChunks: {
name: false,
chunks: `all`,
cacheGroups: {
default: false,
vendors: false,
commons: {
name: `commons`,
chunks: `all`,
// if a chunk is used more than half the components count,
// we can assume it's pretty global
minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
},
react: {
name: `commons`,
chunks: `all`,
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
},
Ao otimizar a configuração do webpack para adotar uma estratégia de divisão granular semelhante, eles também notamos reduções consideráveis de JavaScript em muitos sites grandes:
Site | Alteração total de JS | Diferença % |
---|---|---|
https://www.gatsbyjs.org/ | -680 KB | 22% |
https://www.thirdandgrove.com/ | -390 KB | -25% |
https://ghost.org/ | -1,1 MB | 35% |
https://reactjs.org/ | -80 Kb | -8% |
Confira o RP para entender como eles implementaram essa lógica na configuração de webpack, que é fornecida por padrão na v2.20.7.
Conclusão
O conceito de envio de blocos granulares não é específico para Next.js, Gatsby ou mesmo webpack. Todos devem considerar melhorar a estratégia de divisão do aplicativo caso ela siga um grande "comum" abordagem de pacote, independente do framework ou do bundler de módulo usado.
- Se você quiser ver as mesmas otimizações de divisão aplicadas a um aplicativo básico do React, confira este exemplo de React app. Ele usa uma versão simplificada da estratégia de divisão granular e pode ajudar você a aplicar a mesma estratégia de lógica para seu site.
- Em Rollup, os blocos são criados granular por padrão. Analise
manualChunks
se quiser fazer isso manualmente e configurar o comportamento.