Параметр externals в Webpack используется для исключения
определённых зависимостей из итогового бандла. Вместо включения кода
зависимости внутрь сборки Webpack оставляет обращение к внешнему
источнику.
Наиболее распространённые сценарии:
Без externals Webpack по умолчанию включает
импортируемые зависимости в бандл.
Пример:
import React from 'react';
Обычная сборка встроит React внутрь бандла. При использовании
externals React останется внешней зависимостью.
module.exports = {
externals: {
react: 'React'
}
};
Здесь:
react — имя модуля в import или
require;React — глобальная переменная, существующая во внешней
среде.Webpack заменит импорт на обращение к глобальному объекту:
const React = window.React;
или аналогичный механизм в зависимости от платформы.
Один из самых популярных вариантов — подключение библиотек через CDN.
module.exports = {
externals: {
jquery: 'jQuery'
}
};
<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
<script src="bundle.js"></script>
import $ from 'jquery';
$('.menu').addClass('active');
Внутри bundle.js jQuery отсутствует. Предполагается, что
библиотека уже загружена до выполнения бандла.
Крупные библиотеки значительно увеличивают размер итоговой сборки:
При подключении через CDN:
Пример:
externals: {
react: 'React',
'react-dom': 'ReactDOM'
}
При разработке библиотеки включение зависимостей внутрь бандла часто считается ошибкой.
Например, создаётся UI-библиотека для React.
Неправильный подход:
my-library
└── React внутри бандла
Если приложение пользователя уже использует React, возникнут:
Правильный подход — вынести React во внешнюю зависимость.
module.exports = {
externals: {
react: 'react'
}
};
Теперь библиотека требует React от конечного проекта.
Обычно externals используется вместе с
peerDependencies.
{
"peerDependencies": {
"react": "^18.0.0"
}
}
module.exports = {
externals: {
react: 'react'
}
};
Смысл:
peerDependencies сообщает npm о внешней
зависимости;externals сообщает Webpack не включать зависимость в
бандл.Для Node.js-проектов часто используется CommonJS-формат.
module.exports = {
target: 'node',
externals: {
express: 'commonjs express'
}
};
Webpack не встроит Express в бандл.
Вместо этого останется:
require('express');
Webpack поддерживает несколько типов внешних зависимостей.
externals: {
lodash: '_'
}
externals: {
lodash: 'commonjs lodash'
}
externals: {
lodash: 'commonjs2 lodash'
}
externals: {
lodash: 'amd lodash'
}
externals: {
lodash: 'umd lodash'
}
externals: {
lodash: 'system lodash'
}
module.exports = {
externals: {
lib: 'commonjs lib'
}
};
Генерируется:
exports["lib"] = require("lib");
module.exports = {
externals: {
lib: 'commonjs2 lib'
}
};
Генерируется:
module.exports = require("lib");
Разница особенно важна при разработке библиотек.
Можно задавать несколько внешних зависимостей.
module.exports = {
externals: {
react: 'React',
jquery: 'jQuery',
vue: 'Vue'
}
};
module.exports = {
externals: {
react: [
'React',
'react'
]
}
};
Используется редко и преимущественно в сложных multi-target сборках.
externals может быть функцией.
module.exports = {
externals: [
({ request }, callback) => {
if (/^@app\//.test(request)) {
return callback(null, 'commonjs ' + request);
}
callback();
}
]
};
Это позволяет:
Популярный серверный сценарий.
npm install webpack-node-externals --save-dev
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
externals: [nodeExternals()]
};
Теперь:
node_modules исключаются из бандла;require;Node.js уже умеет загружать зависимости через файловую систему.
Встраивание модулей:
Поэтому серверные сборки почти всегда используют externals.
Некоторые пакеты не могут корректно работать после бандлинга:
bcrypt;sqlite3;sharp;node-sass.Их рекомендуется исключать:
externals: {
sharp: 'commonjs sharp'
}
Webpack 5 ввёл параметр externalsType.
module.exports = {
externalsType: 'commonjs',
externals: {
express: 'express'
}
};
Теперь тип не нужно повторять у каждой зависимости.
Основные варианты:
var
module
assign
this
window
self
global
commonjs
commonjs2
commonjs-module
amd
umd
system
promise
import
script
node-commonjs
Поддержка ES-модулей.
module.exports = {
experiments: {
outputModule: true
},
externalsType: 'module',
externals: {
lodash: 'lodash'
}
};
Webpack создаст внешний ESM-импорт:
import lodash from 'lodash';
Позволяет автоматически загружать внешний скрипт.
module.exports = {
externalsType: 'script',
externals: {
jquery: [
'https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js',
'$'
]
}
};
Webpack:
resolve.alias и externals решают совершенно
разные задачи.
Меняет путь модуля.
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
Исключает модуль из бандла.
externals: {
react: 'React'
}
Разделяет код на чанки.
Зависимость остаётся частью сборки.
Полностью убирает зависимость из сборки.
После сборки можно проверить содержимое бандла.
Если dependency встроена:
React source code...
Если используется externals:
module.exports = React;
или:
require("react")
При использовании CDN порядок подключения критически важен.
Неправильно:
<script src="bundle.js"></script>
<script src="react.js"></script>
Ошибка:
React is not defined
Правильно:
<script src="react.js"></script>
<script src="bundle.js"></script>
При внешних зависимостях особенно важно контролировать версии.
Например:
Library built for React 18
App uses React 16
Возможны:
externals и Module Federation решают похожие задачи, но
разными способами.
Можно исключать зависимости по шаблону.
module.exports = {
externals: [
/^@company\//
]
};
Все пакеты:
@company/ui
@company/core
@company/utils
станут внешними.
module.exports = {
externals: [
{
react: 'React'
},
/^@company\//,
function ({ request }, callback) {
if (request.includes('legacy')) {
return callback(null, 'commonjs ' + request);
}
callback();
}
]
};
Webpack обработает правила последовательно.
Webpack 5 добавил externalsPresets.
module.exports = {
externalsPresets: {
node: true
}
};
Webpack автоматически корректно обрабатывает встроенные Node-модули:
Без externals:
import fs from 'fs';
Webpack попытается обработать модуль.
С externals:
externals: {
fs: 'commonjs fs'
}
останется:
require('fs')
Uncaught ReferenceError
Причина:
externals: {
react: 'React'
}
Но CDN экспортирует:
window.react
Возникнет ошибка.
Если dependency исключена, но нигде не подключена, приложение не запустится.
externals: [
/^src\//
]
Внутренние модули перестанут попадать в сборку.
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'index.js',
library: {
type: 'umd'
}
},
externals: {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
root: 'React'
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'react-dom',
root: 'ReactDOM'
}
}
};
Webpack позволяет задавать отдельные варианты для разных систем модулей.
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}
Это особенно важно для UMD-библиотек.
externals не участвует в tree shaking.
Webpack не анализирует внешний модуль, поскольку он отсутствует в графе зависимостей.
Следствия:
Если библиотека исключена из бандла:
Не рекомендуется:
externals: {
react: 'React',
vue: 'Vue'
}
externals: [nodeExternals()]
externals: {
react: 'react',
'react-dom': 'react-dom'
}
externals: {
react: 'React'
}
externals фактически изменяет границу ответственности
системы.
Без externals:
Приложение полностью владеет зависимостями
С externals:
Часть зависимостей передаётся внешнему окружению
Это влияет на:
Во время построения dependency graph Webpack:
Именно поэтому externals существенно ускоряет сборку крупных проектов.