Финальный бандл в Rollup формируется не механическим объединением файлов, а результатом статического анализа графа модулей и последующей оптимизации. Любой фрагмент кода, попавший в итоговый файл, имеет конкретное объяснение: он либо был импортирован напрямую, либо признан имеющим побочные эффекты, либо оказался необходимым для сохранения корректной семантики программы.
Ключевой принцип, определяющий состав бандла, заключается в том, что удаление кода возможно только при гарантированной безопасности его исключения. Именно здесь возникают основные сложности при отладке: даже «неиспользуемый» код может сохраняться из-за косвенных зависимостей или некорректной интерпретации побочных эффектов.
Rollup строит граф зависимостей, начиная с входных точек. Каждый
import фиксируется как ребро графа, а содержимое модуля
анализируется на предмет:
Если модуль включён в граф, он уже потенциально может попасть в бандл. Исключение возможно только при одновременном выполнении условий:
На практике большинство «непонятно откуда взявшегося» кода сохраняется из-за побочных эффектов.
Побочный эффект в контексте Rollup — любое выполнение кода при загрузке модуля:
Rollup консервативен: если модуль потенциально имеет side effects, он сохраняется целиком.
// utils.js
console.log('init utils');
export function sum(a, b) {
return a + b;
}
Даже если sum не используется, модуль может остаться в
бандле из-за console.log.
Одним из ключевых источников ошибок tree-shaking является поле
sideEffects в package.json.
sideEffects: false — весь пакет считается чистымБиблиотека заявляет:
{
"sideEffects": false
}
Но фактически содержит:
import './polyfills.js';
Если polyfills изменяют глобальную среду, но пакет объявлен «чистым», результатом может стать:
Rollup поддерживает интерпретацию аннотаций вида:
/*#__PURE__*/
Они сигнализируют, что вызов функции не имеет побочных эффектов:
const instance = /*#__PURE__*/ createInstance();
Если результат не используется, вызов может быть удалён.
При использовании @rollup/plugin-commonjs поведение
tree-shaking резко меняется.
CommonJS не предоставляет статически анализируемых экспортов, поэтому:
module.exports = {
a() {},
b() {}
};
Если используется только a, Rollup не всегда может
гарантированно удалить b.
import() создаёт отдельные чанки и влияет на анализ:
import('./module.js').then(m => m.run());
Причины попадания кода в бандл:
Основная задача отладки — не гадать, а видеть структуру итогового кода.
Один из наиболее практичных инструментов:
Причины полезности:
Sourcemaps позволяют связать код бандла с исходными модулями:
output: {
sourcemap: true
}
При отладке важно учитывать:
Rollup позволяет получать информацию о том, почему модуль включён:
onwarnbuildStart, resolveId,
load, transform)Особенно полезен анализ transform, где можно
увидеть:
Tree-shaking в Rollup основан на статическом анализе, но имеет фундаментальные ограничения:
const mod = condition ? require('./a') : require('./b');
mod.run();
Такой код приводит к сохранению обеих веток.
При включённом:
preserveModules: true
каждый модуль сохраняется как отдельный файл.
Это часто воспринимается как «в бандл попало лишнее», хотя фактически:
Если модуль не помечен как external, он может попасть в бандл целиком.
external: ['react']
При отсутствии настройки:
Типовые причины:
export default {
...utils
};
Последовательная проверка причин включения кода обычно сводится к следующему:
Некоторые признаки прямо указывают на причину включения кода:
Плагины Rollup могут существенно менять итоговый результат:
Особенно критично:
Любой фрагмент кода в итоговом bundle объясняется комбинацией факторов:
Разбор конкретного случая всегда сводится к обратной трассировке: от куска бандла к модулю, от модуля к причине сохранения в графе.