Во время сборки проекта Rollup анализирует граф модулей и определяет, какие зависимости должны быть включены в итоговый бандл, а какие останутся внешними и будут загружаться отдельно. От правильного выбора между bundled и external зависимостями напрямую зависят размер сборки, совместимость пакета, скорость загрузки приложения и удобство распространения библиотек.
В экосистеме Rollup все зависимости условно делятся на две категории:
Рассмотрим каждую категорию подробно.
Bundled-зависимость представляет собой модуль, код которого Rollup включает в итоговую сборку.
Исходный проект:
// main.js
import { sum } from './utils.js';
console.log(sum(5, 10));
// utils.js
export function sum(a, b) {
return a + b;
}
После сборки Rollup может создать файл:
function sum(a, b) {
return a + b;
}
console.log(sum(5, 10));
Модуль utils.js больше не существует как отдельный файл.
Его содержимое встроено непосредственно в результат.
Именно такой процесс называется bundling.
Во время сборки Rollup:
Например:
// math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// main.js
import { add } from './math.js';
console.log(add(2, 3));
После сборки:
function add(a, b) {
return a + b;
}
console.log(add(2, 3));
Функция subtract() будет удалена как неиспользуемая.
External-зависимость не включается в результирующий бандл.
Вместо вставки её кода Rollup оставляет импорт или создаёт ссылку на внешний модуль.
Пример:
import lodash from 'lodash';
console.log(lodash.random(1, 10));
Конфигурация:
export default {
input: 'src/main.js',
external: ['lodash']
};
Результат:
import lodash from 'lodash';
console.log(lodash.random(1, 10));
Код библиотеки lodash в сборку не попадёт.
Причин может быть несколько.
Если библиотека уже присутствует в окружении выполнения, нет смысла включать её повторно.
Например:
external: ['react']
В этом случае React не будет встроен в бандл.
Размер итогового файла может уменьшиться на десятки или даже сотни килобайт.
Предположим, приложение уже использует React:
import React from 'react';
Создаётся собственная библиотека компонентов:
import React from 'react';
Если React встроить в библиотеку, получится две копии React:
Это может привести к ошибкам:
Invalid Hook Call
Hooks can only be called inside the body of a function component
Поэтому React практически всегда объявляется внешней зависимостью.
Чем меньше модулей обрабатывает Rollup, тем быстрее выполняется сборка.
Крупные зависимости могут значительно замедлять процесс бандлинга.
Если зависимость остаётся внешней, пользователь библиотеки может самостоятельно выбирать её версию.
Например:
{
"peerDependencies": {
"react": "^18.0.0"
}
}
Разработчик приложения сможет использовать собственную версию React.
Самый простой вариант:
export default {
input: 'src/main.js',
external: ['react']
};
Можно указать несколько пакетов:
external: [
'react',
'react-dom',
'lodash'
]
Для сложных сценариев применяется функция.
export default {
external(id) {
return id === 'react';
}
};
Если функция возвращает true, модуль считается
внешним.
Часто требуется исключить целую группу модулей.
export default {
external(id) {
return id.startsWith('@company/');
}
};
Будут исключены:
@company/core
@company/ui
@company/utils
Иногда библиотека должна содержать только собственный код.
export default {
external(id) {
return !id.startsWith('.') &&
!id.startsWith('/');
}
};
Любой пакет из npm станет внешним.
Примеры:
react
lodash
rxjs
axios
Все они останутся external.
Для конечных приложений обычно используется противоположный подход.
Пример:
import axios from 'axios';
import dayjs from 'dayjs';
Пользователь сайта не обязан отдельно устанавливать эти пакеты.
Поэтому Rollup включает их в сборку.
Структура:
src/
├─ main.js
├─ api.js
└─ components/
После сборки:
dist/
└─ bundle.js
Весь необходимый код находится внутри одного файла или нескольких чанков.
Для библиотек ситуация сложнее.
Рассмотрим пакет:
import React from 'react';
import clsx from 'clsx';
Здесь обычно выбирается следующий подход:
external: ['react']
а clsx включается в бандл.
Причина проста:
Итог:
React → external
clsx → bundled
Очень часто external используется вместе с peerDependencies.
Файл package.json:
{
"peerDependencies": {
"react": "^18.0.0"
}
}
Конфигурация Rollup:
export default {
external: ['react']
};
Такой подход считается стандартным для библиотек компонентов.
Рассмотрим пакет:
{
"dependencies": {
"lodash": "^4.17.21"
}
}
Зависимость устанавливается автоматически.
Другой вариант:
{
"peerDependencies": {
"react": "^18.0.0"
}
}
React должен присутствовать у потребителя библиотеки.
Поэтому большинство peerDependencies одновременно становятся external.
Особое внимание требуется при сборке в формат UMD.
Конфигурация:
export default {
input: 'src/index.js',
external: ['react'],
output: {
format: 'umd',
name: 'MyLibrary',
globals: {
react: 'React'
}
}
};
Здесь необходимо указать объект globals.
Без него Rollup не будет знать, какая глобальная переменная соответствует внешнему модулю.
Получится конструкция вида:
(factory(global.React));
В браузере ожидается наличие:
<script src="react.js"></script>
которая создаёт объект:
window.React
Во многих проектах external формируется автоматически.
Пример:
import pkg from './package.json';
export default {
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
]
};
Rollup будет автоматически исключать зависимости из package.json.
Это особенно удобно для библиотек с большим количеством пакетов.
Включение зависимости обычно оправдано в следующих случаях:
Пример:
import nanoid from 'nanoid';
Небольшие утилиты часто удобнее встроить в итоговую сборку.
External чаще используется, если:
Типичные примеры:
react
react-dom
vue
angular
svelte
rxjs
Иногда разработчик предполагает, что модуль является external, но на практике он всё равно попадает в сборку.
Проверить это можно несколькими способами:
Если внутри dist обнаруживается код сторонней
библиотеки, значит она была включена в процесс bundling.
Для приложений обычно применяется следующая модель:
Собственный код → bundled
npm-зависимости → bundled
Общие чанки → bundled
Пользователь получает полностью готовую сборку.
Для библиотек чаще используется другая модель:
Собственный код → bundled
Мелкие утилиты → bundled
React/Vue и аналоги → external
peerDependencies → external
Такой подход позволяет уменьшить размер пакета, избежать конфликтов версий и предоставить потребителю полный контроль над ключевыми зависимостями.
| Характеристика | Bundled | External |
|---|---|---|
| Попадает в итоговый файл | Да | Нет |
| Увеличивает размер бандла | Да | Нет |
| Требует отдельной установки | Нет | Обычно да |
| Участвует в Tree Shaking | Да | Нет |
| Контролируется Rollup | Полностью | Частично |
| Подходит для приложений | Да | Иногда |
| Подходит для библиотек | Избирательно | Очень часто |
| Исключает дублирование крупных фреймворков | Нет | Да |
Понимание различий между bundled и external зависимостями является одной из ключевых частей проектирования сборки в Rollup. Именно на этом уровне принимаются решения о размере итогового пакета, совместимости библиотек и способе распространения JavaScript-кода.