Условия для работы Tree Shaking: sideEffects и ESM

Tree Shaking в Webpack опирается на статический анализ модулей ES и на явные сигналы о побочных эффектах кода. Без выполнения двух ключевых условий — использования ESM и корректной настройки sideEffects — механизм удаления неиспользуемого кода либо работает частично, либо полностью отключается на уровне оптимизации.

Tree Shaking возможен только там, где система модулей позволяет статический анализ зависимостей. ES Modules (ESM) предоставляют такую возможность за счёт синтаксиса import и export, который не зависит от выполнения кода.

Статическая структура импортов

ESM-модули анализируются до выполнения:

import { formatDate } from './date.js';

Такая конструкция однозначно определяет, что из модуля date.js требуется только formatDate. Это позволяет сборщику:

  • определить используемые экспортируемые символы
  • построить граф зависимостей
  • исключить неиспользуемые экспорты

В отличие от ESM, CommonJS (require) не предоставляет статической структуры:

const date = require('./date');

Значение date может быть вычислено динамически, что делает невозможным точное определение используемых частей модуля без выполнения кода.

Ограничения CommonJS

При использовании CommonJS Webpack вынужден:

  • либо включать весь модуль целиком
  • либо выполнять дополнительные эвристики
  • либо полностью отключать Tree Shaking для таких зависимостей

Даже при включённом optimization.usedExports эффективность анализа резко снижается.

Влияние режима сборки

Tree Shaking активируется только в production-режиме или при явной настройке:

mode: 'production'

В этом режиме включаются:

  • TerserPlugin для удаления мёртвого кода
  • usedExports для анализа используемых экспортов
  • concatenateModules (scope hoisting)

Однако даже при этих оптимизациях без ESM значительная часть потенциала Tree Shaking теряется.

usedExports как промежуточный шаг анализа

Перед удалением кода Webpack должен определить, какие экспорты реально используются.

optimization: {
  usedExports: true
}

Этот режим помечает экспорты как используемые или неиспользуемые, но не удаляет их физически. Результат выражается в аннотациях:

/* unused harmony export foo */

Эти метки затем используются минификатором для удаления кода.

sideEffects как второй обязательный механизм

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

Определение побочных эффектов

Побочный эффект — это любое действие модуля, которое влияет на окружение вне системы модулей:

  • изменение глобальных переменных
  • регистрация полифиллов
  • модификация прототипов
  • импорт CSS
  • инициализация библиотек

Пример:

// polyfill.js
window.fetch = function () {};

Даже если экспортов нет, сам факт импорта изменяет глобальное окружение.

Флаг sideEffects в package.json

Механизм Tree Shaking использует поле sideEffects в package.json:

{
  "sideEffects": false
}

Значение false

Если указано false, Webpack считает:

  • все модули пакета чистыми
  • любые неиспользуемые импорты можно безопасно удалять

Это максимальный уровень агрессивности Tree Shaking.

Массив исключений

Часто полная чистота невозможна, особенно при наличии CSS:

{
  "sideEffects": [
    "*.css",
    "*.scss"
  ]
}

В этом случае:

  • JS-модули считаются чистыми
  • CSS и SCSS всегда выполняются при импорте

Почему sideEffects критически важен

Без sideEffects Webpack не может безопасно удалить импорт:

import './polyfills';

Даже если модуль не экспортирует ничего, он может:

  • изменять окружение
  • регистрировать глобальные функции
  • запускать инициализацию

Если флаг отсутствует, Webpack по умолчанию считает, что побочные эффекты есть.

Взаимодействие ESM и sideEffects

Tree Shaking становится эффективным только при одновременном выполнении двух условий:

1. ESM позволяет анализ экспорта

  • статическая структура импортов
  • отсутствие динамического require
  • предсказуемые зависимости

2. sideEffects позволяет удалять модули

  • подтверждает безопасность удаления
  • разрешает исключать неиспользуемые файлы целиком

Если отсутствует хотя бы один компонент, оптимизация деградирует.

Пример полного Tree Shaking

Исходный код

// math.js
export function sum(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// unused function
export function debugLog() {
  console.log('debug');
}
import { sum } from './math.js';

console.log(sum(2, 3));

Результат анализа

Webpack:

  • определяет, что используется только sum
  • помечает multiply и debugLog как неиспользуемые
  • удаляет их на этапе минификации

sideEffects и CSS-импорты

CSS является классическим примером побочных эффектов:

import './styles.css';

Такой импорт:

  • не имеет экспортов
  • всегда выполняется
  • влияет на DOM через style injection

Поэтому CSS нельзя помечать как полностью tree-shakable код, но можно включать в исключения sideEffects.

Конкатенация модулей (Scope Hoisting)

Дополнительный слой оптимизации усиливает Tree Shaking:

optimization: {
  concatenateModules: true
}

Webpack объединяет модули в единый scope, уменьшая:

  • количество функций-обёрток
  • уровень косвенности вызовов
  • сложность анализа для минификатора

Это усиливает удаление мёртвого кода, особенно в связке с ESM.

Ограничения Tree Shaking

Даже при идеальной настройке существуют ограничения:

Динамические выражения

const mod = await import('./' + name);

Такие конструкции разрушают статический анализ.

Side effects внутри модулей

export const value = init();

Если init() имеет побочные эффекты, удаление становится небезопасным.

Реэкспорт через index-файлы

export * from './moduleA';

Webpack должен развернуть цепочку экспортов, что усложняет анализ.

Практическая модель принятия решений Webpack

Tree Shaking проходит несколько этапов:

  1. построение графа модулей
  2. анализ импортов (ESM)
  3. маркировка used exports
  4. проверка sideEffects
  5. удаление неиспользуемого кода
  6. минификация результата

Каждый этап зависит от предыдущего, и отсутствие ESM или корректного sideEffects прерывает цепочку оптимизации.

Роль библиотек и экосистемы

Современные библиотеки ориентируются на Tree Shaking:

  • публикация в формате ESM
  • указание sideEffects: false
  • разделение чистого и побочного кода
  • предоставление modular API вместо монолитных импортов

Такой подход позволяет сборщику выполнять агрессивное удаление кода без риска изменения поведения приложения.