Чистые функции и инлайнинг

Роль чистых функций в процессе сборки

В основе эффективного 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.

Связь чистых функций и tree-shaking

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;

Таким образом, цепочка оптимизаций выглядит так:

  1. выявление чистой функции;
  2. инлайнинг вызова;
  3. алгебраическое упрощение выражений;
  4. удаление мёртвого кода, если результат не используется.

Условия безопасного инлайнинга

Rollup применяет строгие критерии, чтобы не нарушить поведение программы:

1. Отсутствие побочных эффектов Любое изменение внешнего состояния делает функцию кандидатом на отказ от инлайнинга.

2. Локальность зависимостей Если функция использует внешние переменные, которые могут изменяться, инлайнинг становится опасным.

let factor = 2;

function mul(x) {
  return x * factor;
}

Значение factor может измениться, поэтому инлайнинг требует осторожности.

3. Предсказуемость вызова Функции с динамическими свойствами, такими как arguments, this, или eval, ограничивают возможности оптимизации.


Инлайнинг в контексте модулей ES

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) ограничивают оптимизацию.

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

Комбинация чистых функций и инлайнинга приводит к:

  • уменьшению количества функций в итоговом коде;
  • сокращению накладных расходов на вызовы;
  • увеличению эффективности minify-стадий;
  • более агрессивному tree-shaking.

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