ES-модули как целевой формат

ES-модули (ECMAScript Modules, ESM) представляют собой стандартную систему модулей JavaScript, основанную на статическом анализе зависимостей через конструкции import и export. В контексте Rollup этот формат является основным и наиболее «нативным» целевым результатом сборки, поскольку архитектура бандлера изначально оптимизирована под работу со статическим графом импортов.

Базовое представление ES-модулей в Rollup

При указании формата вывода es Rollup генерирует код, который сохраняет модульную структуру приложения или библиотеки, минимально трансформируя исходные import и export:

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'es'
  }
};

Результат такой сборки сохраняет ESM-синтаксис:

// dist/bundle.js
export { something } from './module.js';
import { helper } from './helper.js';

function main() {
  return helper();
}

export { main };

В отличие от форматов iife или cjs, здесь отсутствует оборачивание в функцию или CommonJS-объекты. Это позволяет использовать итоговый файл напрямую в современных браузерах и в Node.js при включённой поддержке ESM.

Статический анализ и влияние на сборку

Ключевая особенность ES-модулей заключается в их статической природе. Rollup использует это для построения полного графа зависимостей на этапе сборки. Это означает, что:

  • все импорты известны до выполнения кода;
  • невозможно динамически изменить структуру модулей в рантайме;
  • удаление неиспользуемого кода (tree-shaking) становится детерминированным.

Пример влияния статического анализа:

// util.js
export function used() {
  return 'used';
}

export function unused() {
  return 'unused';
}

// index.js
import { used } from './util.js';

console.log(used());

Rollup при format: "es" исключит unused, поскольку она не входит в граф использования.

Tree-shaking как основное преимущество ES-формата

ES-модули являются основой tree-shaking в Rollup. Так как экспортируемые сущности определяются статически, Rollup может безопасно удалять неиспользуемые части кода.

Важный аспект заключается в том, что эффективность tree-shaking зависит не только от Rollup, но и от структуры самого кода:

export const a = 1;
export const b = 2;
export const c = 3;

Если используется только a, остальные экспорты исключаются при корректной конфигурации.

Однако tree-shaking может быть нарушен побочными эффектами:

export function init() {
  console.log('side effect');
}

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

Свойство sideEffects и влияние на ES-вывод

Для оптимальной работы ES-формата важную роль играет поле sideEffects в package.json:

{
  "sideEffects": false
}

Это позволяет Rollup и другим инструментам (например, Webpack) безопасно удалять неиспользуемые модули целиком.

Если указано:

{
  "sideEffects": ["*.css"]
}

то CSS-файлы сохраняются, а JS-модули продолжают оптимизироваться.

preserveModules и сохранение структуры

При использовании ES-формата часто применяется опция preserveModules, которая сохраняет исходную структуру файлов:

export default {
  input: 'src/index.js',
  output: {
    dir: 'dist',
    format: 'es',
    preserveModules: true
  }
};

Результат:

dist/
  index.js
  utils/
    helper.js

Каждый исходный модуль становится отдельным файлом. Это особенно важно для библиотек, которые:

  • должны поддерживать глубокий импорт (import { x } from 'lib/utils');
  • используются в tree-shakable окружениях;
  • ориентированы на современные сборщики.

Отличие ES-формата от CommonJS

При сравнении ES-модулей и CommonJS в Rollup выявляются фундаментальные различия:

Характеристика ES modules (es) CommonJS (cjs)
Анализ зависимостей статический динамический
Tree-shaking полноценный ограниченный
Поддержка браузеров нативная отсутствует
Экспорт named/default module.exports
Оптимизация высокая средняя

Rollup при format: "es" избегает преобразования модулей в CommonJS-обёртки, что снижает накладные расходы и сохраняет прозрачность зависимостей.

Динамические импорты и code splitting

ES-модули поддерживают динамический импорт через import():

export async function loadModule() {
  const module = await import('./heavy.js');
  return module.run();
}

Rollup использует эту конструкцию для автоматического code splitting при format: "es":

export default {
  input: 'src/main.js',
  output: {
    dir: 'dist',
    format: 'es',
    chunkFileNames: 'chunks/[name]-[hash].js'
  }
};

Результатом становится разделение кода на чанки:

dist/
  main.js
  chunks/
    heavy-abc123.js

Интеграция с браузерами и Node.js

ES-модули в формате Rollup полностью совместимы с современными окружениями.

В браузере:

<script type="module" src="/dist/main.js"></script>

В Node.js (при "type": "module"):

{
  "type": "module"
}

И импорт:

import { main } from './dist/main.js';

Важно учитывать расширения файлов: в Node.js ESM требуется явное указание .js.

Влияние внешних зависимостей (external)

При сборке в ES-формате часто требуется исключение внешних библиотек:

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/index.js',
    format: 'es'
  },
  external: ['react', 'lodash']
};

Это приводит к сохранению импортов:

import React from 'react';
import { debounce } from 'lodash';

Rollup не включает эти зависимости в бандл, сохраняя ES-синтаксис.

Interop и смешанные модули

При работе с библиотеками, использующими CommonJS, Rollup выполняет преобразование через плагины (например, @rollup/plugin-commonjs). В ES-выводе это может приводить к дополнительным обёрткам:

import pkg from 'cjs-lib';
const { method } = pkg;

Важно учитывать, что такие преобразования могут частично снижать преимущества чистого ES-формата.

Оптимизация вывода ES-модулей

Для максимальной эффективности ES-вывода применяются следующие практики:

  • использование чистых ESM-зависимостей;
  • избегание динамического require;
  • минимизация побочных эффектов;
  • структурирование кода в мелкие модули;
  • использование явных экспортов вместо export * в критичных участках.

Пример оптимальной структуры:

// math/add.js
export function add(a, b) {
  return a + b;
}

// math/multiply.js
export function multiply(a, b) {
  return a * b;
}

// index.js
export { add } from './math/add.js';
export { multiply } from './math/multiply.js';

Формирование итогового ES-бандла

Rollup при ES-выводе стремится сохранить читаемость и модульность, а не минимизировать количество файлов любой ценой. В отличие от UMD или IIFE форматов, здесь приоритетом является:

  • сохранение структуры импортов;
  • поддержка tree-shaking на уровне потребителя;
  • совместимость с будущими сборочными инструментами.

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

Поведение при минификации

ES-формат не предполагает минификацию как часть своей модели. Однако он часто используется вместе с @rollup/plugin-terser:

import terser from '@rollup/plugin-terser';

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'es'
  },
  plugins: [terser()]
};

Минификация в этом случае не меняет модульную структуру, а только упрощает синтаксис:

export function a(n,o){return n+o}

Ограничения ES-формата в Rollup

Несмотря на универсальность, ES-вывод имеет ограничения:

  • невозможность использовать в старых браузерах без транспиляции;
  • зависимость от поддержки ESM в окружении;
  • потенциальные сложности с legacy CommonJS пакетами;
  • необходимость строгой структуры импортов.

Эти ограничения компенсируются за счёт современных окружений и инструментов транспиляции (Babel, TypeScript).

Роль ES-модулей в архитектуре библиотек

ES-формат стал стандартом для публикации библиотек, ориентированных на tree-shaking. Rollup использует его как основной режим для:

  • UI-библиотек;
  • утилитарных пакетов;
  • модульных SDK;
  • фронтенд-фреймворков.

Типичная конфигурация библиотеки:

export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'es'
    },
    {
      file: 'dist/index.cjs.js',
      format: 'cjs'
    }
  ]
};

ES-версия при этом остаётся основной точкой оптимизации.

Итоговая модель поведения ES-вывода в Rollup

ES-модули в Rollup формируют промежуточный слой между исходным кодом и конечным окружением исполнения. Они обеспечивают:

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