Встряхивание дерева - это метод, используемый при удалении мертвого кода, который направлен на уменьшение размера пакета за счет удаления неиспользуемых функций, импорта и операторов. Это также называется включение живого кода. Его популяризирует RollupJS, но идея пришла из 1990-х годов из LISP.

ОПРЕДЕЛЕНИЕ ПРОБЛЕМЫ

Было сложно найти неиспользуемые блоки кода на уровне оператора или на уровне файла для динамических сценариев, таких как JavaScript, из-за их природы. Вы можете динамически изменять поток своего приложения во время выполнения, что может влиять на глобальные области, и это может быть сделано с помощью CommonJS.

… Что это значит? Не может ли встряхивание дерева применяться к CommonJS?

Да, это также может быть применено к CommonJS, однако будет действительно сложно найти неиспользуемые блоки кода, а не модули ES6, поэтому древовидная схема не будет так хороша, как модули ES6.

… Но почему?

Импорт и экспорт модуля ES6 являются статическими, поэтому вы не можете условно импортировать файлы. Приведем пример;

// CommonJS require which is a valid statement
if(smth === 61){
 var lodash = require(‘lodash’);
 lodash.difference();
}
// ES6 import which will give error
if(smth === 61){
 import {difference} from ‘lodash’;
 difference();
}

В приведенном выше блоке кода, хотя реализация CommonJS действительна, реализация ES6 выдаст ошибку. Если вы можете что-то динамически импортировать, как вы можете определить, используется этот файл или нет? Во-первых, вы должны быть уверены в том, что оператор if (строка 2) никогда не будет выполняться. Если вы в этом уверены, то можете безопасно удалить его со всего if-блока. Однако принять решение для оператора if не так-то просто.

Это самый сложный момент при определении неиспользуемого импорта, когда вы можете импортировать файлы динамически, и именно поэтому модули ES6 могут быть древовидными, потому что вы не можете динамически импортировать файлы.

КАК ЭТО РАБОТАЕТ

По природе ES6, как я упоминал выше, легче находить неиспользуемые блоки кода с помощью анализа. Процесс встряхивания дерева начинается с точки входа. Каждый оператор импорта добавляется как лист дерева, включая его зависимости, а также зависимости его зависимостей. Каждый лист, на котором есть ссылка, помечается зеленым, а неиспользуемые - серым, затем просто отрежьте серые, и он готов к работе.

Попробуем настроить встряхивание деревьев для Rollup, Webpack и Parcel.

Репозиторий: https://github.com/muratcatal/tree-shaking-comparision

Код ниже является точкой входа в наш образец, и он импортируется как в модулях ES6, так и в типе CommonJS.

// index.js
import * as math from "./math";
import * as lodash from "./lodash";
import { concat } from "./helper";
require("./cmn");
concat("Hello");
lodash.difference([1, 2, 3]);
const helloVar = "hello-var";
console.log('Hello world!');

Свернуть

Рич Харрис, создатель RollupJS, написал в блоге о своем новом проекте Rollup и упомянул о его потрясающей функции «встряхивания дерева». В нем также упоминается, насколько сложно удалить неиспользуемые блоки кода на динамическом языке и некоторые нехватки функций при встряхивании дерева, но это был первый сборщик, который реализует идею встряхивания дерева, поэтому другие библиотеки, такие как Webpack, были вдохновлены.

Чтобы включить встряхивание дерева в Rollup, мы должны установить наш выходной формат на ESM

Давайте настроим наш накопительный пакет;

import resolve from 'rollup-plugin-node-resolve';
export default {
    input: './index.js',
    output: {
        file: './dist/rollup-main.js',
        format: 'esm'
    },
    plugins: [
        resolve()
    ]
};

Это все, что вам нужно сделать, чтобы Rollup использовал встряхивание дерева. Если вы не используете CommonJS, ваш код будет импортирован в процесс tree-shake, и вы получите хороший результат.

В этом примере я объединил модули узлов в приложение, и чтобы позволить Rollup, как работать с модулями узлов, управляет подключаемый модуль rollup-plugin-node-resolve.

Когда я запускаю yarn build: rollup, он выполняет свою работу, как ожидалось, и все неиспользуемые операции импорта и операторы будут удалены из пакета, за исключением оператора require ().

Webpack

Функция «встряхивания дерева», реализованная в версии 2.2 для Webpack и улучшенная в версии 4. В отличие от Rollup, вы должны запускать Webpack в производственном режиме, чтобы он «встряхивал» ваш код. В дополнение к этому; вы можете изменить поведение для процесса с тремя встряхиваниями с помощью двух нижеуказанных флагов конфигурации.

По умолчанию, когда webpack работает в производственном режиме, sideEffects и usedExports по умолчанию имеют значение true. Поскольку значение usedExports истинно, ваш веб-пакет будет собирать данные о ваших неиспользуемых экспортных данных и предоставлять эту информацию терсеру или любым другим плагинам tree-shake. Обычно вам не нужно устанавливать значение true для производственной конфигурации.

Когда вы запускаете свой Webpack с помощью - display-used-exports, он дает хороший результат, который показывает информацию об операторах импорта.

побочные эффекты

import 'math.js';

В приведенном выше примере я только что импортировал файл math.js и нигде не использовал его, однако можете ли вы быть уверены, что он не имеет побочных эффектов в вашем проекте или в вашем файле javascript, куда он импортируется?

Чтобы сказать Webpack, что неиспользуемые экспорты не имеют побочных эффектов и их можно безопасно удалить из дерева, если они не используются, используется флаг sideEffects. Вы можете установить для него значение false в файле package.json, чтобы отключить весь проект, или вы можете указать массив, чтобы указать, какие файлы имеют побочные эффекты.

Установка для sideEffects значения false увеличит процесс сборки, однако вы получите дрожание дерева, которое уменьшит размер вашего пакета.

// package.json 
{
  sideEffects: false,
  sideEffects: ['./math']
}

используйте один из вариантов использования. вы можете сказать, что ваш сборщик не трогает математическую библиотеку, потому что у него есть побочный эффект, или вы можете сказать, что ваш проект сборщика не имеет побочного эффекта, поэтому он может безопасно удалить неиспользуемый экспорт

usedExports

Это полезно для удаления неиспользуемых операторов и полагается на terser для обнаружения неиспользуемых операторов. Вы можете включить его в конфигурации своего веб-пакета.

module.exports = {
  //...
  optimization: {
    usedExports: true
  }
};

sideEffects намного эффективнее, потому что пропускает весь файл импорта.

Чтобы воспользоваться преимуществами встряхивания дерева в Webpack;

  • Создайте свой код в производственном режиме
  • Использовать модули ES6 (импорт и экспорт)
  • Если вы используете @ babel / preset-env от babel, он преобразует ваш ES6 в CommonJS, что делает невозможным использование встряхивания дерева. Однако вы можете дать своему babel возможность поддерживать ESM.
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "esmodules": true
        }
      }
    ]
  ]
}
// alternative way to tell babel not to transpile ESM modules into CommonJS
{
  "presets": [
    ["env",
      {
        "modules": false
      }
    ]
  ]
}

Когда я запускаю yarn build: webpack, результат будет похож на Rollup.

Parsel

Parsel основан на сборщике с нулевой конфигурацией, что позволяет легко адаптировать ваши проекты. Встряхивание деревьев появилось в версии 1.9, однако это все еще экспериментальная функция, поэтому вы должны использовать флаг, чтобы Parsel запускал свои алгоритмы встряхивания деревьев при сборке проекта.

parcel build index.js --experimental-scope-hoisting

Как упоминалось в первом разделе, встряхивание дерева для модулей CommonJS действительно сложно, однако в Parcel встроена эта замечательная функция.

Когда я запускаю yarn build: parcel, он показывает разницу, и операторы console.log и concat («Hello») будут удалены. Однако всякий раз, когда я назначал переменной concat («Привет»), parcel включал ее в пакет.

Так что же дальше?

Встряхивание дерева - это приятная и мощная функция, когда она используется эффективно, поскольку она уменьшает размер вашего пакета, а также избавляет вас от неожиданных запусков кода. (Вы можете подумать, недоступен ли оператор и как поток приложения может упасть в этом состоянии, но не игнорируйте, что ваш JavaScript может запускаться внешними одноранговыми узлами без вашего разрешения на стороне клиента)

Обработка процесса встряхивания дерева может быть разной для разных сборщиков пакетов, но, в конце концов, то, как вы пишете свой код, значительно влияет на этот процесс.

использованная литература