При сборке серверных приложений на Node.js возникает важная задача: исключить встроенные модули Node.js и некоторые зависимости из итогового бандла. В браузерной среде Webpack обычно стремится включить все зависимости внутрь сборки, однако для серверного кода подобное поведение часто оказывается нежелательным.
Webpack 5 ввёл механизм externalsPresets, позволяющий
быстро активировать предустановленные наборы поведения для различных
платформ выполнения. Для Node.js этот механизм особенно важен, поскольку
серверная среда уже содержит собственные встроенные API:
fspathhttpcryptostreamosurleventsБез правильной конфигурации Webpack может пытаться обрабатывать эти модули как обычные зависимости, что приводит к ошибкам, полифилам или некорректной сборке.
externalsPresetsТипичный серверный код:
const fs = require('fs');
const path = require('path');
console.log(fs.readFileSync(path.resolve(__dirname, 'file.txt')));
Если собрать такой проект без специальных настроек:
module.exports = {
target: 'node'
};
Webpack может:
Особенно заметны проблемы после миграции с Webpack 4 на Webpack 5, где автоматические полифилы Node.js были удалены.
externalsPresetsМинимальная настройка для Node.js:
module.exports = {
target: 'node',
externalsPresets: {
node: true
}
};
Значение:
node: true
активирует специальный режим, в котором Webpack:
require;node: trueКонфигурация:
externalsPresets: {
node: true
}
автоматически исключает из сборки:
require('fs')
require('path')
require('http')
require('https')
require('stream')
require('crypto')
В результирующем бандле они остаются в исходном виде:
const fs = require('fs');
Webpack не заменяет их внутренними реализациями.
target: 'node' и externalsPresets.nodeМногие разработчики ошибочно считают, что target: 'node'
полностью решает задачу серверной сборки.
На практике эти параметры выполняют разные функции.
target: 'node'Определяет:
__dirname;Пример:
module.exports = {
target: 'node'
};
externalsPresets.nodeОпределяет:
Пример:
module.exports = {
externalsPresets: {
node: true
}
};
Практически всегда для Node.js используется комбинация:
module.exports = {
target: 'node',
externalsPresets: {
node: true
}
};
externalsexternalsPresets часто используется совместно с
externals.
Пример:
module.exports = {
target: 'node',
externalsPresets: {
node: true
},
externals: {
express: 'commonjs express'
}
};
Здесь:
express также исключается вручную.В итоговом коде:
const express = require('express');
останется без изменений.
webpack-node-externalsВ серверных проектах часто применяется пакет:
npm install webpack-node-externals
Пример конфигурации:
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
externalsPresets: {
node: true
},
externals: [nodeExternals()]
};
Такой подход:
node_modules;webpack-node-externalsДопустим, проект использует:
npm install express lodash mongoose
При конфигурации:
externals: [nodeExternals()]
Webpack не будет включать:
expresslodashmongooseв итоговый файл.
Вместо этого сохранится:
require('express')
require('lodash')
require('mongoose')
На сервере зависимости уже присутствуют в
node_modules.
Поэтому нет смысла:
Для утилит командной строки:
my-cli build
обычно важны:
Главный процесс Electron работает в Node.js-среде.
Поэтому конфигурация:
externalsPresets: {
node: true
}
часто обязательна.
Иногда зависимости необходимо встроить внутрь бандла.
Например:
В подобных случаях:
externals: []
может быть предпочтительнее.
Webpack 5 поддерживает Node.js ESM-сборки.
Пример:
module.exports = {
target: 'node',
experiments: {
outputModule: true
},
externalsPresets: {
node: true
},
output: {
module: true
}
};
Webpack умеет генерировать разные типы внешних импортов.
externals: {
lodash: 'commonjs lodash'
}
Результат:
require('lodash')
externalsType: 'module',
externals: {
lodash: 'lodash'
}
Результат:
import lodash from 'lodash';
externalsTypeПример:
module.exports = {
target: 'node',
externalsPresets: {
node: true
},
externalsType: 'commonjs'
};
Возможные значения:
commonjsmodulevarscriptumdthiswindowДля Node.js обычно используется:
commonjs
или
module`
Без externalsPresets пришлось бы писать:
externals: {
fs: 'commonjs fs',
path: 'commonjs path',
crypto: 'commonjs crypto',
stream: 'commonjs stream',
os: 'commonjs os'
}
При большом количестве модулей это неудобно.
externalsPresets.node автоматизирует процесс.
node:-префиксовСовременный Node.js поддерживает синтаксис:
import fs from 'node:fs';
Webpack 5 корректно работает с такими импортами при:
externalsPresets: {
node: true
}
Пример:
const moduleName = 'fs';
const lib = require(moduleName);
Webpack анализирует такие конструкции хуже, чем статические импорты.
Использование externalsPresets.node снижает вероятность
ошибочной обработки встроенных модулей.
Без externals:
bundle.js → 12 MB
С externals:
bundle.js → 400 KB
Особенно заметна разница при использовании:
Исключение зависимостей:
Для крупных backend-проектов разница может быть существенной.
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
mode: 'production',
target: 'node',
entry: './src/server.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'server.js'
},
externalsPresets: {
node: true
},
externals: [nodeExternals()]
};
Конфигурация:
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
externalsPresets: {
node: true
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader'
}
]
},
resolve: {
extensions: ['.ts', '.js']
}
};
module.exports = {
target: 'node',
externalsPresets: {
node: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
}
]
}
};
target: 'node'Ошибка:
externalsPresets: {
node: true
}
без:
target: 'node'
может привести к браузерному runtime.
Некоторые библиотеки должны попадать в бандл.
Например:
externals: [nodeExternals({
allowlist: ['webpack/hot/poll?100']
})]
Если приложение запускается без node_modules, externals
приведут к ошибке:
Cannot find module 'express'
После сборки полезно проверить:
require('fs');Полезно использовать:
webpack --json > stats.json
и анализаторы:
webpack-bundle-analyzer
или:
speed-measure-webpack-plugin
node configuration sectionВ старых версиях Webpack использовался раздел:
node: {
__dirname: false
}
Webpack 5 делает акцент именно на:
externalsPresets
Эти механизмы решают разные задачи.
Webpack поддерживает несколько preset-наборов:
externalsPresets: {
node: true,
electron: true,
electronMain: true,
electronRenderer: true,
web: true
}
electronMainДля Electron main process:
externalsPresets: {
electronMain: true
}
Webpack автоматически учитывает:
electronRendererДля renderer process:
externalsPresets: {
electronRenderer: true
}
Поведение отличается от обычного браузера, поскольку renderer может иметь доступ к Node.js API.
webPreset:
externalsPresets: {
web: true
}
ориентирован на браузерную среду.
Для backend-приложений он обычно не используется.
При активации:
externalsPresets: {
node: true
}
Webpack подключает внутренний список built-in модулей Node.js.
Среди них:
assert
buffer
child_process
cluster
crypto
dgram
dns
events
fs
http
https
net
os
path
stream
tls
url
util
worker_threads
zlib
Все они рассматриваются как external dependency.
Типичная современная серверная конфигурация:
const nodeExternals = require('webpack-node-externals');
module.exports = {
mode: 'production',
target: 'node',
externalsPresets: {
node: true
},
externals: [nodeExternals()],
optimization: {
minimize: false
}
};
Особенности: