Articles

Tree Shaking

Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. Baseia-se na estrutura estática da sintaxe do módulo ES2015, ou seja, import e export. O nome e conceito foram popularizados pelo rollup do módulo de pacotes ES2015.

a versão do webpack 2 veio com suporte incorporado para módulos ES2015 (alias harmony modules), bem como detecção de exportação de módulos não utilizados. O novo webpack versão 4, expande essa capacidade com uma forma de fornecer dicas para o compilador através de "sideEffects"package.json propriedade para indicar quais arquivos em seu projeto é “pura” e, portanto, seguro para podar caso não seja utilizado.

Adicionar um Utilitário

Vamos adicionar um novo utilitário de arquivo para o nosso projecto, src/math.js, que exporta duas funções:

o projeto

webpack-demo|- package.json|- webpack.config.js|- /dist |- bundle.js |- index.html|- /src |- index.js+ |- math.js|- /node_modules

src/matemática.js

export function square(x) { return x * x;}export function cube(x) { return x * x * x;}

Set the mode configuration option to development to make sure that the bundle is not minified:

webpack.configuracao.js

const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },+ mode: 'development',+ optimization: {+ usedExports: true,+ },};

Com isso no lugar, vamos atualizar o nosso script de entrada para utilizar um desses novos métodos e remover lodash para simplificar:

src/index.js

- import _ from 'lodash';+ import { cube } from './math.js'; function component() {- const element = document.createElement('div');+ const element = document.createElement('pre');- // Lodash, now imported by this script- element.innerHTML = _.join(, ' ');+ element.innerHTML = .join('\n\n'); return element; } document.body.appendChild(component());

Observe que nós não importsquare método src/math.js módulo. Essa função é o que é conhecido como “código morto”, significando um não usado export que deve ser descartado. Agora vamos executar o nosso script npm, npm run build, e inspecionar o pacote de saída:

dist / bundle.js (em torno das linhas 90 – 100)

/* 1 *//***/ (function (module, __webpack_exports__, __webpack_require__) { 'use strict'; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__ = cube; function square(x) { return x * x; } function cube(x) { return x * x * x; }});

Note the unused harmony export square comment above. Se você olhar para o código abaixo dele, você vai notar que square não está sendo importado, no entanto, ele ainda está incluído no Pacote. Vamos tratar disso na próxima secção.

marque o ficheiro como sem efeitos secundários

num mundo de módulos mes a 100%, a identificação dos efeitos secundários é simples. No entanto, ainda não estamos lá, então, entretanto, é necessário fornecer dicas para o compilador do webpack sobre a “pureza” do seu código.

A forma como isto é realizado é o"sideEffects" pacote.propriedade json.

{ "name": "your-project", "sideEffects": false}

Todo o código mencionado acima não contém efeitos colaterais, para que possamos basta marcar a propriedade false para informar webpack que é capaz de podar não utilizados exportações.

Se o seu código, têm alguns efeitos colaterais, porém, uma matriz pode ser fornecido em vez disso:

{ "name": "your-project", "sideEffects": }

A matriz aceita simples glob padrões para os arquivos relevantes. Ele usa glob-para-regexp sob o capô (Suporte: ***{a,b}). Padrões como o *.css, que não incluem um / será tratado como **/*.css.

{ "name": "your-project", "sideEffects": }

Finally,"sideEffects" can also be set from themodule.rules configuration option.

Clarifying tree shaking and sideEffects

ThesideEffects andusedExports (more known as tree shaking) optimizations are two different things.

sideEffects é muito mais eficaz, uma vez que permite saltar módulos/ficheiros inteiros e a sub-árvore completa.

usedExports depende de terser para detectar efeitos secundários em declarações. It is a difficult task in JavaScript and not as effective as straightforward sideEffects flag. Também não pode saltar sub-árvore/dependências uma vez que a especificação diz que os efeitos secundários precisam ser avaliados. Embora a função de exportação funcione bem, os componentes de ordem superior da React (HOC) são problemáticos a este respeito.

Vamos fazer um exemplo:

import { Button } from '@shopify/polaris';

O pré-empacotadas versão se parece com isso:

import hoistStatics from 'hoist-non-react-statics';function Button(_ref) { // ...}function merge() { var _final = {}; for ( var _len = arguments.length, objs = new Array(_len), _key = 0; _key < _len; _key++ ) { objs = arguments; } for (var _i = 0, _objs = objs; _i < _objs.length; _i++) { var obj = _objs; mergeRecursively(_final, obj); } return _final;}function withAppProvider() { return function addProvider(WrappedComponent) { var WithProvider = /*#__PURE__*/ (function (_React$Component) { // ... return WithProvider; })(Component); WithProvider.contextTypes = WrappedComponent.contextTypes ? merge(WrappedComponent.contextTypes, polarisAppProviderContextTypes) : polarisAppProviderContextTypes; var FinalComponent = hoistStatics(WithProvider, WrappedComponent); return FinalComponent; };}var Button$1 = withAppProvider()(Button);export { // ..., Button$1,};

Quando Button não é utilizada efetivamente, você pode remover o export { Button$1 }; o que deixa todo o restante do código. Então a questão é: “este código tem algum efeito colateral ou pode ser removido com segurança?”. Difícil de dizer, especialmente por causa desta linha withAppProvider()(Button) é chamado e o valor de retorno também é chamado. Existem efeitos secundários ao chamar mergeouhoistStatics? Existem efeitos secundários ao atribuir WithProvider.contextTypes (Setter?) ou ao ler WrappedComponent.contextTypes (Getter?).

Terser realmente tenta descobrir, mas não tem certeza em muitos casos. Isso não significa que terser não está fazendo seu trabalho bem porque não pode descobri-lo. É muito difícil determinar isso de forma confiável em uma linguagem dinâmica como JavaScript.

But we can help terser by using the /*#__PURE__*/ annotation. Ele sinaliza uma declaração como um efeito colateral livre. Assim, uma simples mudança tornaria possível agitar o código:

var Button$1 = /*#__PURE__*/ withAppProvider()(Button);

isto permitiria remover esta peça de código. Mas ainda há dúvidas sobre as importações que precisam ser incluídas/avaliadas porque podem conter efeitos colaterais.

para resolver isso, usamos o "sideEffects" propriedade empackage.json.

é semelhante a mas a um nível de módulo em vez de um nível de instrução. It says ("sideEffects" property): “If no direct export from a module flaged with no-sideEffects is used, the bundler can skip evaluating the module for side effects.”.no exemplo Polaris do Shopify, os módulos originais se parecem com este:

índice.js

import './configure';export * from './types';export * from './components';

componentes/índice.js

// ...export { default as Breadcrumbs } from './Breadcrumbs';export { default as Button, buttonFrom, buttonsFrom } from './Button';export { default as ButtonGroup } from './ButtonGroup';// ...

package.json

// ..."sideEffects": ,// ...

import { Button } from "@shopify/polaris"; este tem as seguintes implicações:

  • incluir: incluir o módulo, avaliá-lo e prosseguir a análise de dependências
  • ignorar: não incluí-lo, não avaliá-lo, mas prosseguir a análise de dependências
  • excluir ele: não incluí-lo, não avaliá-lo, e não analisar dependências

Especificamente por correspondência de recurso(s):

  • index.js: Nenhuma exportação direta é usada, mas sinalizado com efeitos colaterais -> incluir
  • configure.js: Não exportação é usado, mas sinalizado com efeitos colaterais -> incluir
  • types/index.js: Não exportação é usado, não sinalizado com efeitos colaterais -> excluir
  • components/index.js: Nenhuma exportação direta é usada, não sinalizado com efeitos colaterais, mas reexportado exportações são usados> ignorar
  • components/Breadcrumbs.js: Não é usada nenhuma exportação, não assinalada com ‘sideEffects’ – > exclua-a. Isto também excluiu todas as dependências como components/Breadcrumbs.css mesmo que estejam assinaladas com efeitos laterais.
  • components/Button.js: exportação Direta é usada, não sinalizado com efeitos colaterais -> incluir
  • components/Button.css: Não exportação é usado, mas sinalizado com efeitos colaterais -> incluir

neste caso, apenas a 4 módulos estão incluídos no pacote:

  • index.js: praticamente vazio
  • configure.js
  • components/Button.js
  • components/Button.css

Após essa otimização, outras otimizações, podem ainda se aplicam. Por exemplo: buttonFrom e buttonsFrom exportações de Button.js também não são utilizadas. usedExports optimization will pick it up and terser may be able to drop some statements from the module.também se aplica a concatenação do módulo. De modo que estes 4 módulos mais o módulo de entrada (e provavelmente mais dependências) podem ser concatenados. index.js não tem nenhum código gerado no final.

marque uma chamada de função como sem efeito colateral

é possível dizer ao webpack que uma chamada de função é livre de efeito colateral (puro) usando o anotação. Ele pode ser colocado na frente de chamadas de funções para marcá-los como sem efeito colateral. Argumentos passados à função não estão sendo marcados pela anotação e podem precisar ser marcados individualmente. Quando o valor inicial em uma declaração variável de uma variável não usada é considerado como sem efeito colateral (puro), ele está ficando marcado como código morto, não executado e deixado cair pelo minimizador. Este comportamento é ativado quando optimization.innerGraph é definido para true.ficheiro

.js

/*#__PURE__*/ double(55);

Minify a Saída

Então, nós temos dicas de nosso “código morto” para ser descartados usando o import e export sintaxe, mas ainda precisamos de retirá-lo do conjunto. Para isso, defina a opção de configuração mode para production.

webpack.configuracao.js

const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },- mode: 'development',- optimization: {- usedExports: true,- }+ mode: 'production',};

com esse quadrado afastado, podemos executar outro npm run build e ver se alguma coisa mudou.

Notice anything different about ? Claramente, todo o conjunto é agora reduzido e mutilado, mas, se você olhar com cuidado, você não verá o square função incluída, mas vai ver um deturpados versão de cube função (function r(e){return e*e*e}n.a=r). Com a minificação e a árvore a tremer, o nosso pacote é agora alguns bytes menores! Embora isso possa não parecer muito neste exemplo inventado, a agitação de árvores pode produzir uma diminuição significativa no tamanho do pacote ao trabalhar em aplicações maiores com árvores de dependência complexas.

conclusão

então, o que nós aprendemos é que, a fim de tirar proveito da tremedeira da árvore, você deve…

  • Use ES2015 module syntax (i.e. import and export).
  • certifique-se de que nenhum compilador transforma a sintaxe do módulo ES2015 em módulos CommonJS (este é o comportamento padrão da popular predefinição Babel @babel/preset – env-veja a documentação para mais detalhes).
  • Add a "sideEffects" property to your project’s package.json file.
  • Use a opção de configuraçãoproductionmode para permitir várias optimizações, incluindo a minificação e a agitação das árvores.

pode imaginar a sua aplicação como uma árvore. O código fonte e as bibliotecas que você realmente usa representam as folhas verdes e vivas da árvore. O código morto representa as folhas castanhas e mortas da árvore que são consumidas pelo outono. A fim de se livrar das folhas mortas, você tem que sacudir a árvore, fazendo-os cair.

Se você está interessado em mais maneiras de otimizar a sua saída, por favor, salte para o próximo guia para detalhes sobre a construção para a produção.