Побочные эффекты (side effects) и их влияние на встряску

Tree shaking в Rollup опирается на статический анализ ES-модулей и удаление неиспользуемых экспортов. Однако эффективность этого механизма напрямую зависит от наличия побочных эффектов в модулях. Побочные эффекты (side effects) — это любое поведение модуля, которое сохраняется при его импорте независимо от использования его экспортов.

Понятие побочных эффектов в контексте модулей

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

Типичные примеры:

  • изменение глобальных объектов (window, globalThis)
  • регистрация обработчиков событий
  • модификация прототипов
  • выполнение сетевых запросов при импорте
  • логирование в момент загрузки модуля
  • импорт CSS или других ресурсов, которые обрабатываются сборщиком
// side-effectful-module.js
window.appVersion = "1.0.0";

export function sum(a, b) {
  return a + b;
}

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

Tree shaking и проблема побочных эффектов

Tree shaking в Rollup работает на уровне графа зависимостей и анализирует:

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

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

import "./analytics.js"; // может быть оставлен даже без использования экспорта

Если analytics.js выполняет регистрацию трекинга при загрузке, его удаление изменит поведение приложения.

Влияние package.json и поле sideEffects

В экосистеме JavaScript основным механизмом управления побочными эффектами на уровне пакета является поле sideEffects в package.json.

{
  "name": "my-lib",
  "sideEffects": false
}

Значение этого поля:

  • false — весь пакет считается без побочных эффектов
  • массив файлов — перечисление файлов с побочными эффектами
  • отсутствие поля — неопределённое поведение для сборщика

Пример с частичными побочными эффектами:

{
  "sideEffects": [
    "*.css",
    "src/polyfills.js"
  ]
}

Rollup и плагины могут использовать эту информацию для более агрессивного удаления неиспользуемого кода.

Важность статической предсказуемости

Tree shaking требует, чтобы код был максимально предсказуемым:

// плохо для tree shaking
if (Math.random() > 0.5) {
  doSomething();
}

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

В контексте побочных эффектов важно различать:

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

Rollup может анализировать только статические случаи.

Pure-annotations и их роль

Для более точного tree shaking используются специальные комментарии, обозначающие чистоту вызова:

/*@__PURE__*/ createLogger();

Такая аннотация позволяет Rollup и минификаторам удалять вызов, если его результат не используется.

Пример:

const logger = /*#__PURE__*/ createLogger();

Если logger не используется дальше, весь вызов может быть удалён.

Однако это работает только при соблюдении условий:

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

Модули с косвенными побочными эффектами

Особую сложность представляют модули, которые не содержат явных side effects, но вызывают их через импорты:

import "./polyfills.js";
import "./setupGlobals.js";

Даже если текущий модуль чистый, его зависимости могут быть нет.

Это создаёт цепочку влияния:

entry → moduleA → moduleB (side effects)

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

CSS и немодульные импорты

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

import "./styles.css";

CSS не экспортирует JS-значения, но его подключение влияет на внешний вид приложения.

Поэтому такие импорты почти всегда помечаются как side effects и не удаляются tree shaking-ом.

JSON и данные без кода

JSON-модули обычно считаются чистыми:

import data from "./data.json";

Однако сам факт подключения может быть оптимизирован только если данные действительно используются. Если импорт не применяется, Rollup может удалить его, но только при отсутствии side effects в цепочке.

Влияние CommonJS и интеропа

При работе с CommonJS модулями ситуация усложняется:

  • невозможно точно определить побочные эффекты на этапе анализа
  • require() выполняется динамически
  • экспорт не всегда статически анализируем
const lib = require("legacy-lib");

Такие модули часто полностью исключаются из tree shaking или обрабатываются консервативно.

Оптимизации Rollup в контексте side effects

Rollup применяет несколько стратегий:

  • удаление неиспользуемых экспортов
  • сворачивание модулей без side effects
  • инлайнинг функций
  • устранение мёртвого кода

Но всё это ограничено наличием потенциальных побочных эффектов.

Ключевой принцип:

если модуль может изменить поведение приложения при импорте — он сохраняется

Ошибки конфигурации sideEffects

Неправильная настройка sideEffects приводит к критическим проблемам:

  • удаление CSS или инициализационного кода
  • исчезновение polyfill-логики
  • нарушение инициализации библиотек
{
  "sideEffects": false
}

При таком значении Rollup может удалить даже импорт, который на самом деле выполняет важную регистрацию.

Локальные побочные эффекты внутри функций

Побочные эффекты не всегда связаны с импортом:

export function init() {
  window.config = loadConfig();
}

Даже если init не вызывается, сам факт его существования не создаёт side effects, но его вызов — да. Tree shaking работает на уровне вызова, а не определения.

Гранулярность удаления кода

Rollup удаляет код на нескольких уровнях:

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

Но наличие side effects снижает гранулярность до уровня модуля.

Итоговая роль side effects в оптимизации

Побочные эффекты являются ограничивающим фактором tree shaking. Они формируют границу между безопасной оптимизацией и сохранением поведения программы. Чем точнее описаны side effects, тем более агрессивно Rollup может удалять неиспользуемый код, уменьшая итоговый бандл и улучшая производительность загрузки.