Настройки treeshake.tryCatchDeoptimization

Tree-shaking в Rollup основан на статическом анализе ES-модулей и удалении неиспользуемого кода на этапе сборки. Однако эффективность этого процесса напрямую зависит от того, насколько точно сборщик может определить побочные эффекты выражений и конструкций. Одним из менее очевидных, но критически важных параметров, влияющих на этот анализ, является treeshake.tryCatchDeoptimization.

В основе оптимизации Rollup лежит анализ графа зависимостей и пометка кода как:

  • имеющего побочные эффекты
  • не имеющего побочных эффектов
  • неопределённого по эффектам (side-effect unknown)

Конструкции try/catch попадают в последнюю категорию в силу того, что Rollup не всегда может гарантировать отсутствие скрытых побочных эффектов внутри try блока, особенно при взаимодействии с импортируемыми модулями.

try {
  someImportedFunction();
} catch (e) {}

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

Проблема деоптимизации через исключения

Любой try/catch блок влияет на оптимизацию по двум причинам:

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

В результате Rollup применяет стратегию деоптимизации: он начинает считать содержимое try потенциально опасным для удаления.

Это приводит к тому, что даже «чистые» вызовы внутри try могут быть сохранены в бандле, несмотря на отсутствие фактического использования результата.

Параметр treeshake.tryCatchDeoptimization

Настройка treeshake.tryCatchDeoptimization управляет тем, насколько агрессивно Rollup будет снижать оптимизации внутри try/catch конструкций.

Поведение по умолчанию

По умолчанию параметр включён:

export default {
  treeshake: {
    tryCatchDeoptimization: true
  }
};

При этом Rollup:

  • считает содержимое try блока потенциально имеющим побочные эффекты
  • ограничивает удаление кода внутри try
  • снижает точность tree-shaking в сложных цепочках вызовов

Влияние на анализ побочных эффектов

При включённой деоптимизации происходит расширение области «неопределённости»:

  • вызовы функций внутри try чаще считаются side-effectful
  • импортируемые символы реже удаляются
  • цепочки переэкспорта сохраняются даже при частичном использовании

Пример:

import { pureFn } from './utils.js';

try {
  pureFn();
} catch (e) {}

Даже если pureFn является чистой функцией, Rollup может сохранить вызов из-за невозможности гарантировать отсутствие побочных эффектов.

Отключение tryCatchDeoptimization

Отключение параметра:

export default {
  treeshake: {
    tryCatchDeoptimization: false
  }
};

меняет стратегию анализа. Rollup перестаёт автоматически считать try блок зоной пониженной оптимизации.

Последствия отключения

Отключение приводит к более агрессивному tree-shaking:

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

Пример:

try {
  pureUtility();
} catch {}

При tryCatchDeoptimization: false вызов может быть полностью исключён, если pureUtility признана чистой.

Взаимодействие с sideEffects-аннотациями

Эффект tryCatchDeoptimization тесно связан с другими механизмами Rollup:

  • package.json: sideEffects
  • treeshake.moduleSideEffects
  • treeshake.propertyReadSideEffects

Если модуль помечен как не имеющий побочных эффектов:

{
  "sideEffects": false
}

то даже внутри try/catch Rollup может агрессивно удалять код, особенно при отключённой деоптимизации.

Однако при включённой настройке tryCatchDeoptimization эта оптимизация частично блокируется.

Влияние на динамические сценарии

Особенно заметно влияние параметра в следующих случаях:

1. Работа с плагинами и полифилами

Многие полифилы используют try/catch для проверки поддержки API:

try {
  globalThis.crypto.randomUUID();
} catch {
  fallback();
}

При включённой деоптимизации Rollup может сохранить весь блок, даже если crypto.randomUUID доступен в окружении.

2. Ленивая инициализация модулей

let init;

try {
  init = require('./init.js');
} catch {}

Здесь require (особенно в CommonJS-interop) становится причиной полной сохранности блока.

3. Обёртки безопасности

export function safeCall(fn) {
  try {
    return fn();
  } catch {}
}

Даже при очевидной чистоте fn Rollup может не исключить вызов внутри функции-обёртки.

Сложности статического анализа

Основная проблема, которую решает tryCatchDeoptimization, заключается не в самом try/catch, а в невозможности:

  • предсказать тип ошибки
  • гарантировать отсутствие side effects
  • различить контроль потока и бизнес-логику

JavaScript не предоставляет информации о «типе чистоты» функций на уровне языка, поэтому Rollup вынужден опираться на эвристики.

Практические последствия для бандла

При включённой деоптимизации:

  • увеличивается размер итогового бандла
  • снижается количество удалённых импортов
  • повышается стабильность (меньше риск удаления нужного кода)

При отключённой:

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

Связь с производительностью сборки

Хотя параметр влияет в основном на результат, а не на скорость сборки, косвенные эффекты присутствуют:

  • больше сохранённого кода → более тяжёлый AST на последующих этапах плагинов
  • меньше агрессивных упрощений → больше узлов в графе модулей

В крупных проектах это может влиять на время финальной оптимизации и минификации.

Поведенческая модель Rollup

Логика можно описать упрощённо:

  • без try/catch → точный анализ чистоты
  • с try/catch + деоптимизация → консервативная модель
  • с try/catch + отключённая деоптимизация → оптимистичная модель

Эта разница становится критичной в библиотеках, где активно используется функциональный стиль и высокая доля чистых функций.

Ограничения настройки

treeshake.tryCatchDeoptimization не решает фундаментальные проблемы:

  • не различает типы ошибок
  • не анализирует runtime-ветвления
  • не учитывает внешние глобальные состояния

Она лишь управляет уровнем консервативности анализа внутри одной синтаксической конструкции.

В сложных кодовых базах эффект этой настройки часто оказывается вторичным по сравнению с корректной архитектурой модулей и строгим определением side effects.