В основе эффективного tree-shaking в Rollup лежит способность статического анализа определять, какие части кода могут быть безопасно удалены. Одним из ключевых факторов, влияющих на это, являются чистые функции — функции, не имеющие побочных эффектов и всегда возвращающие одинаковый результат при одинаковых входных данных.
Чистота функции в контексте сборщика означает не только математическую предсказуемость, но и отсутствие взаимодействий с внешним состоянием: запись в глобальные переменные, обращения к DOM, сетевые запросы, мутации импортированных объектов.
Rollup использует анализ чистоты для принятия решений о том, можно ли:
Пример чистой функции:
function sum(a, b) {
return a + b;
}
Такая функция может быть безопасно инлайнена или даже полностью вычислена на этапе сборки:
sum(2, 3) → 5
Инлайнинг — это процесс замены вызова функции её телом. В контексте Rollup он тесно связан с tree-shaking и constant folding.
Рассмотрим базовый пример:
function double(x) {
return x * 2;
}
const result = double(10);
После анализа Rollup может преобразовать код в:
const result = 10 * 2;
А затем, при наличии дополнительной оптимизации:
const result = 20;
Инлайнинг выполняется только в случае, если:
Любое наличие побочных эффектов блокирует агрессивную оптимизацию.
let counter = 0;
function inc() {
counter++;
return counter;
}
Такая функция не может быть инлайнена без изменения поведения программы, поскольку каждый вызов изменяет внешнее состояние.
Rollup рассматривает подобные случаи как небезопасные для трансформации, даже если результат кажется предсказуемым в локальном контексте.
@__PURE__Одним из ключевых механизмов управления оптимизациями является
комментарий /*@__PURE__*/.
Он сообщает сборщику, что вызов функции можно считать чистым, если он не имеет внешних последствий.
const value = /*@__PURE__*/ createConfig();
Если результат вызова нигде не используется, Rollup может удалить его полностью:
/*@__PURE__*/ createConfig();
// удаляется
Особенности поведения:
Tree-shaking и инлайнинг взаимосвязаны: чем более «чистым» является код, тем глубже Rollup может его оптимизировать.
Рассмотрим пример модуля:
export function pureAdd(a, b) {
return a + b;
}
export function logAdd(a, b) {
console.log(a + b);
return a + b;
}
При использовании:
import { pureAdd } from './math.js';
pureAdd(2, 3);
Rollup:
pureAdd;logAdd, если оно не используется;pureAdd в вызывающем коде.Инлайнинг часто является промежуточным этапом перед constant folding — свёрткой выражений.
function square(x) {
return x * x;
}
const a = square(4);
После инлайнинга:
const a = 4 * 4;
После свёртки:
const a = 16;
Таким образом, цепочка оптимизаций выглядит так:
Rollup применяет строгие критерии, чтобы не нарушить поведение программы:
1. Отсутствие побочных эффектов Любое изменение внешнего состояния делает функцию кандидатом на отказ от инлайнинга.
2. Локальность зависимостей Если функция использует внешние переменные, которые могут изменяться, инлайнинг становится опасным.
let factor = 2;
function mul(x) {
return x * factor;
}
Значение factor может измениться, поэтому инлайнинг
требует осторожности.
3. Предсказуемость вызова Функции с динамическими
свойствами, такими как arguments, this, или
eval, ограничивают возможности оптимизации.
Rollup работает с ES-модулями как с графом зависимостей. Это позволяет ему безопасно анализировать чистоту функций между файлами.
// util.js
export const add = (a, b) => a + b;
// main.js
import { add } from './util.js';
console.log(add(1, 2));
После сборки:
console.log(1 + 2);
Здесь происходит сразу несколько шагов:
add определяется как чистая;Стиль написания кода напрямую влияет на возможность Rollup применять инлайнинг.
Функции высшего порядка:
const makeAdder = (x) => (y) => x + y;
Такие конструкции часто сложнее для анализа, поскольку включают замыкания. Rollup может частично инлайнить внутренние функции, но сохраняет структуру при наличии динамических зависимостей.
Чрезмерная абстракция через функции и утилиты может быть сведена к прямым операциям:
const isEven = (n) => n % 2 === 0;
const arr = [1, 2, 3, 4].filter(isEven);
После оптимизации:
const arr = [1, 2, 3, 4].filter(n => n % 2 === 0);
Далее возможны дополнительные оптимизации, если массив известен на этапе сборки.
Несмотря на мощный статический анализ, Rollup не выполняет полноценное исполнение кода. Поэтому:
window,
process) ограничивают оптимизацию.Комбинация чистых функций и инлайнинга приводит к:
В результате конечный бандл часто превращается из набора абстракций в набор прямых выражений и минимальных вычислений, где функции существуют только там, где они действительно необходимы.