Como o tamanho do código depende do minificador, coletor e idioma. Atualização inesperada do webpack

Meu nome é Ilya Goldfarb, sou desenvolvedor de interfaces Yandex. Estou interessado em acompanhar o desenvolvimento das ferramentas para criar o front-end, por isso tento estudar as mudanças em cada versão de soluções populares.

Em antecipação ao lançamento da quinta versão do webpack, quero falar sobre o lançamento aparentemente menor 4.26.0 de 19 de novembro de 2018, onde, inesperadamente e sem declarar guerra, a versão padrão do minifier foi alterada. Antes era um pacote UglifyJS, agora usa o Terser, um fork do UglifyES, uma filial do UglifyJS que pode compactar o código ES5 e ES6. Terser apareceu quando o principal mantenedor se recusou a apoiar e desenvolver o UglifyES. No entanto, o UglifyJS também interrompeu seu desenvolvimento em agosto de 2018, quando o último lançamento foi lançado. Em um novo fork, corrigimos alguns bugs e refatoramos um pouco o código.

A API desses minificadores é compatível, mas eles produzem resultados de compactação diferentes. Normalmente, as alterações desse nível ocorrem apenas nas atualizações principais, e não menores. Por esse motivo, muitos desenvolvedores podem não prestar atenção à inovação. Obviamente, na maioria dos casos tudo funcionará, mas ninguém quer se tornar aquele que recebe bugs na produção de seu projeto devido ao sistema de compilação e minificação.

Toda essa história me levou a fazer uma pequena pesquisa pessoal sobre compressão. Aqui estão as perguntas que eu fiz:

  • O que comprime ES5, TerSer ou UglifyJS melhor?
  • O que está carregando mais rápido: uma versão compactada do ES5 do Terser ou do UglifyJS?
  • Qual versão pesa mais: ES5 ou ES6? E como o TypeScript afeta isso?
  • Existe uma grande diferença entre as configurações padrão e as manuais?
  • E se não for webpack? Quem produz uma montagem menor, Rollup ou webpack?

Para pesquisa, criei um pequeno aplicativo React 16 que renderiza um aplicativo Vue 2 que renderiza um aplicativo Angular 7 que possui um botão inteiro.

No total, foram liberados 3 529 695 bytes de código não minificado (720 393 gzip bytes).

O que comprime ES5, TerSer ou UglifyJS melhor?


Peguei o último UglifyJS disponível e viesse com o webpack Terser com a opção ES5 e usei as mesmas configurações de compactação.

Tamanho em bytes
Tamanho em bytes (gzip)
UglifyJS
1.050.376
285.290
Terser
1.089.282
292 678
Conclusão: o UglifyJS compacta melhor em 3,5% (2,5% gzip).

O que está carregando mais rápido: uma versão compactada do ES5 do Terser ou do UglifyJS?


Avaliei o desempenho usando o DevTools Yandex Browser padrão. Carreguei a página 12 vezes e peguei o valor Scripting (tempo de execução do script), descartando as três primeiras dimensões.
UglifyJS - 221 ms (erro 2,8%).
Terser - 226 ms (erro 2,7%).
Conclusão: os valores são muito pequenos para esse erro; podemos considerá-los iguais. Concluímos também que esse método não é adequado para medir o tempo de carga.
Não medi e comparei a velocidade do código, pois códigos diferentes funcionam de maneira diferente. Os desenvolvedores de cada projeto devem investigar independentemente esse problema.

Qual versão pesa mais: ES6 ou ES5? E como o TypeScript afeta isso?


Para comparar as duas versões e focar apenas a tecnologia, peguei os plugins da Babel e fiz quatro montagens:

  • ES5: todos os plugins marcados como es2016, + plug-in para Object.assign + plug-ins para versões posteriores + plug-ins experimentais, alvo no tsconfig instalado no ES5;
  • ES5 (ts esnext): todos os plugins marcados como es2016, + plugin para Object.assign + todos os plugins para versões posteriores + plugins experimentais, o destino no tsconfig é definido como esnext;
  • ES6: apenas plug-ins para es2017 e posteriores + plug-ins experimentais, o destino no tsconfig está definido como ES6;
  • ES6 (ts esnext): somente plug-ins para es2017 e versões posteriores + experimentais, o destino no tsconfig está definido como esnext.


Tamanho em bytes
Tamanho em bytes (gzip)
ES5
1 186 520
322 071
ES5 (ts esnext)
1.089.282
292 678
ES6
1.087.220
292 232
ES6 (ts esnext)
1.087.220
292 232
Conclusão: a versão compactada por Babel com o código de tempo de compilação sob esnext pesa 97.238 bytes (8,2%) a menos. Isso aconteceu inesperadamente muito, porque o angular é escrito em TypeScript e o Vue e React no JavaScript Terser, como o Uglify, não pode criar um pedaço de código não utilizado entregue pelo angular com um script da web ao criar com um webpack. Este é um bug de compilação para este exemplo. Na montagem em outro projeto, pode não ser, e a diferença será muito menor.

Também é visto que o volume do código ES6 é menor que ES5 em apenas 2062 bytes. No projeto pet, obtive um resultado completamente diferente: o código ES6 é 3-6% a mais que o ES5. Isso ocorre devido a vários fatores, dos quais dois principais:
1. O auxiliar Babel para herança de classe é inserido uma vez e custa quatro bytes (e (a, b)), e o ES6 usa herança nativa ao custo de 15 bytes (a classe a estende b).
2. O método de declarar variáveis. No ES5, esses são vários e compactam perfeitamente. Mas no ES6, estes são let e const, que preservam a ordem de inicialização e não são combinados entre si.

A minificação agressiva insegura, como as funções de seta forçada ou o uso da configuração solta, ajudará a reduzir o tamanho do código ES6. Tenha cuidado e considere as sutilezas. Por exemplo, no Firefox, as funções de seta são quatro vezes mais lentas que o normal, mas no Chromium não há diferença.

Portanto, é impossível responder inequivocamente à pergunta: o resultado é altamente dependente do código e do tempo de execução do destino.

Existe uma grande diferença entre as configurações padrão e as manuais?


Compare se é possível obter um tamanho menor de arquivo se você ajustar um pouco as configurações. Por exemplo, indicamos que a minificação deve ser repetida cinco vezes. Por padrão, ele passa apenas uma vez.

Tamanho em bytes
Tamanho em bytes (gzip)
Terser (padrão) ES5
1.097.141
294 306
Terser (passa 5) ES5
1 089 312
292.408
Uglify (padrão) ES5
1 091 350
294.845
Uglify (passa 5) ES5
1.050.363
284 618
Conclusão: o Uglify com minificação de cinco vezes é menor que o Uglify por padrão em 3,7% (3,4% gzip). Portanto, você sempre deve apertar as configurações de compactação. A propósito, minificação quíntupla não significa que a montagem durará cinco vezes mais. Por exemplo, neste projeto de teste, uma única minificação leva 18 segundos, cinco vezes - 38 e dez vezes - 49. Eu recomendo encontrar experimentalmente o valor ideal para o seu projeto, após o qual a minificação será interrompida e o código não será alterado. Normalmente, é de 5 a 10. Há também várias outras opções: comments: false corta todos os comentários sobre licenças (embora isso seja um problema legal) e hoist_funs: true groups funciona em um único local, o que permite uma melhor otimização dos vars. Idealmente, você precisa revisar todas as configurações .

Quem produz uma montagem menor, Rollup ou webpack?


O rollup é um coletor alternativo com um mecanismo interno de agitação de árvores. Para o teste, fiz uma compilação no Rollup 0.67.4 com as mesmas configurações do webpack.

Tamanho em bytes
Tamanho em bytes (gzip)
Pacote cumulativo ES5 (Uglify)
990 497
274 105
Pacote cumulativo ES5 (Terser)
995 318
272 532
webpack ES5 (Uglify)
1.050.363
284 618
webpack ES5 (Terser)
1 089 312
292.408
Conclusão: o resultado do Rollup e do Uglify é 5,6% (3,6% gzip) a menos.

Isso aconteceu por vários motivos:

1. O Webpack contém muletas para casos limítrofes. Por exemplo, esse código agrupa cada chamada de função de outro módulo em Object (). Isso é feito para impedir a transferência de contexto para módulos sem uso estrito para módulos com uso estrito. Projetos bem escritos sem dependências de terceiros não precisam de um wrapper, mas às vezes não apenas código bem escrito está envolvido no assembly. E, nesse sentido, o webpack parece mais confiável. Rollap, por sua vez, acredita que todos os módulos são ES6, e eles sempre são executados em uso estrito; portanto, esse problema simplesmente não existe para ele.

Uma pergunta importante é como essas muletas do webpack afetam o desempenho. Imagine que escrevemos o código perfeito que não precisa de invólucros adicionais, mas ainda assim, todas as chamadas de funções passarão por eles. Isso adiciona uma pequena sobrecarga de desempenho: aproximadamente um centésimo de microssegundo por chamada de função no Chromium (um décimo no Firefox).

2. O webpack possui um pequeno bootstrap que controla a inicialização e o carregamento dos módulos. O rollup não usa wrappers, mas simplesmente coloca o código de todos os módulos em um único escopo. O webpack tem uma otimização semelhante, mas não funciona com todos os módulos.

Resumo do Estudo


Espero que muitos, depois de ler o artigo, verifiquem seus sistemas de compilação e garantam que eles usem todos os truques possíveis para obter a melhor compactação. É rápido e fácil.

Primeiro, configure um monte de TypeScript e Babel corretamente. Deixe que cada componente da montagem faça suas próprias coisas: um verifica os tipos e o segundo é responsável pela conversão para padrões obsoletos.

Em segundo lugar, ao usar o ES5, você pode alterar o minificador de volta para UglifyJS, mas lembre-se de que ele não é mais suportado.

Em terceiro lugar, é preferível escolher Rollup para montagem. No entanto, nem todos os casos são possíveis devido à falta de alguns plugins. Após a montagem, não se esqueça de verificar a funcionalidade por testes funcionais. Se você não os tiver, é hora de começar a escrevê-los.

Source: https://habr.com/ru/post/pt438022/


All Articles