Webpack предоставляет не только CLI-интерфейс, но и полноценный программный API для запуска сборки непосредственно из Node.js-кода. Такой подход используется при разработке собственных build-систем, интеграции Webpack в серверные приложения, создании CLI-инструментов, автоматизации CI/CD и построении сложных сценариев компиляции.
В основе API находится модуль webpack, экспортирующий
функцию создания компилятора.
Базовый пример:
const webpack = require('webpack');
const config = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js'
}
};
const compiler = webpack(config);
После вызова webpack(config) создаётся объект
Compiler, содержащий всю внутреннюю инфраструктуру
сборки:
Для использования API необходимы пакеты:
npm install webpack webpack-cli --save-dev
Даже если CLI не используется напрямую, webpack-cli
часто устанавливается для совместимости со сторонними инструментами.
Структура проекта:
project/
├── build.js
├── src/
│ └── index.js
└── dist/
Файл build.js:
const path = require('path');
const webpack = require('webpack');
const config = {
mode: 'development',
entry: path.resolve(__dirname, 'src/index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
const compiler = webpack(config);
compiler.run((err, stats) => {
if (err) {
console.error(err);
return;
}
console.log(
stats.toString({
colors: true
})
);
});
Запуск:
node build.js
compiler.run()Метод run() запускает одиночную компиляцию.
compiler.run((err, stats) => {
});
Аргументы callback:
| Аргумент | Описание |
|---|---|
err |
Критическая ошибка инфраструктуры |
stats |
Объект статистики сборки |
Важно разделять:
Инфраструктурные ошибки появляются при:
Пример:
compiler.run((err, stats) => {
if (err) {
console.error('Fatal webpack error');
console.error(err);
return;
}
});
Ошибки модулей находятся внутри stats.
compiler.run((err, stats) => {
if (stats.hasErrors()) {
console.error(
stats.toJson().errors
);
}
});
Пример ошибки:
Module not found: Error: Can't resolve './app'
StatsStats содержит огромный объём информации о сборке.
Получение JSON-представления:
const info = stats.toJson();
Основные поля:
| Поле | Назначение |
|---|---|
errors |
Ошибки |
warnings |
Предупреждения |
assets |
Список файлов |
modules |
Информация о модулях |
chunks |
Информация о чанках |
hash |
Хэш сборки |
time |
Время компиляции |
Webpack умеет красиво форматировать статистику:
console.log(
stats.toString({
colors: true,
modules: false,
chunks: false
})
);
Популярные параметры:
| Параметр | Описание |
|---|---|
colors |
ANSI-цвета |
modules |
Вывод модулей |
chunks |
Вывод чанков |
assets |
Вывод ресурсов |
entrypoints |
Entrypoints |
children |
Child compilations |
После завершения сборки рекомендуется закрывать compiler.
compiler.run((err, stats) => {
compiler.close(closeErr => {
if (closeErr) {
console.error(closeErr);
}
});
});
Это особенно важно при:
Webpack API построен на callback-модели, однако легко оборачивается в Promise.
function runWebpack(config) {
return new Promise((resolve, reject) => {
const compiler = webpack(config);
compiler.run((err, stats) => {
if (err) {
reject(err);
return;
}
compiler.close(closeErr => {
if (closeErr) {
reject(closeErr);
return;
}
resolve(stats);
});
});
});
}
Использование:
(async () => {
try {
const stats = await runWebpack(config);
console.log(
stats.toString({
colors: true
})
);
} catch (e) {
console.error(e);
}
})();
Webpack поддерживает массив конфигураций.
const compiler = webpack([
clientConfig,
serverConfig
]);
В этом случае создаётся MultiCompiler.
Пример:
const clientConfig = {
name: 'client',
target: 'web'
};
const serverConfig = {
name: 'server',
target: 'node'
};
const compiler = webpack([
clientConfig,
serverConfig
]);
При использовании массива конфигураций stats содержит
дочерние сборки.
compiler.run((err, stats) => {
const children = stats.stats;
for (const child of children) {
console.log(child.compilation.name);
}
});
Программный API поддерживает watcher.
compiler.watch({}, (err, stats) => {
});
Пример:
const watching = compiler.watch(
{
aggregateTimeout: 300,
poll: undefined
},
(err, stats) => {
console.log('Rebuild completed');
}
);
watching.close(err => {
if (err) {
console.error(err);
}
});
| Параметр | Назначение |
|---|---|
aggregateTimeout |
Задержка перед rebuild |
poll |
Polling mode |
ignored |
Игнорируемые пути |
stdin |
Закрытие по stdin |
Пример:
compiler.watch(
{
ignored: /node_modules/,
aggregateTimeout: 500
},
callback
);
Compiler предоставляет систему hooks через Tapable.
Подписка на завершение сборки:
compiler.hooks.done.tap(
'DonePlugin',
stats => {
console.log('Build complete');
}
);
Другие важные хуки:
| Hook | Назначение |
|---|---|
run |
Перед запуском |
watchRun |
Перед rebuild |
compile |
Начало компиляции |
emit |
Перед записью файлов |
afterEmit |
После записи |
done |
После завершения |
failed |
При ошибке |
emitПозволяет изменять assets перед записью.
compiler.hooks.emit.tapAsync(
'CustomPlugin',
(compilation, callback) => {
compilation.assets['meta.txt'] = {
source() {
return 'Build metadata';
},
size() {
return 14;
}
};
callback();
}
);
Compilation содержит состояние конкретной сборки.
Получение через hook:
compiler.hooks.compilation.tap(
'InspectCompilation',
compilation => {
}
);
Внутри доступны:
Webpack позволяет заменить файловую систему.
const MemoryFS = require('memory-fs');
compiler.outputFileSystem = new MemoryFS();
Теперь bundle не записывается на диск.
Чтение результата:
compiler.run((err, stats) => {
const content =
compiler.outputFileSystem.readFileSync(
'/dist/bundle.js',
'utf8'
);
console.log(content);
});
Такой подход активно используется:
Webpack также позволяет переопределять входную файловую систему.
compiler.inputFileSystem = customFs;
Это используется при:
Пример запуска сборки из HTTP-сервера:
const express = require('express');
const webpack = require('webpack');
const app = express();
const compiler = webpack(config);
app.get('/build', (req, res) => {
compiler.run((err, stats) => {
if (err) {
res.status(500).send(err.message);
return;
}
res.send(
stats.toString({
colors: false
})
);
});
});
app.listen(3000);
Программный запуск особенно важен для middleware.
const express = require('express');
const webpack = require('webpack');
const middleware = require('webpack-dev-middleware');
const compiler = webpack(config);
const app = express();
app.use(
middleware(compiler)
);
В этом случае:
Пример минимального CLI:
#!/usr/bin/env node
const webpack = require('webpack');
const config = require('./webpack.config');
const compiler = webpack(config);
compiler.run((err, stats) => {
if (err) {
process.exit(1);
}
console.log(
stats.toString({
colors: true
})
);
});
Конфигурацию можно генерировать динамически.
function createConfig(mode) {
return {
mode,
devtool:
mode === 'development'
? 'eval-source-map'
: false
};
}
const config =
createConfig(process.env.NODE_ENV);
Программный API особенно полезен для runtime-конфигурации.
const config = require('./webpack.config');
config.plugins.push(
new MyPlugin()
);
config.mode = 'production';
const start = Date.now();
compiler.run((err, stats) => {
console.log(
`Build time: ${Date.now() - start}ms`
);
});
Либо:
console.log(stats.endTime - stats.startTime);
async function buildAll() {
await runWebpack(clientConfig);
await runWebpack(serverConfig);
}
await Promise.all([
runWebpack(clientConfig),
runWebpack(serverConfig)
]);
Программный запуск Webpack особенно распространён в SSR-системах.
Схема работы:
require-from-string.const fs = compiler.outputFileSystem;
const bundle = fs.readFileSync(
'/dist/server.js',
'utf8'
);
Watcher поддерживает ручной rebuild.
watching.invalidate();
Это принудительно запускает повторную компиляцию.
Webpack имеет инфраструктурный логгер.
const logger =
compiler.getInfrastructureLogger(
'custom'
);
logger.info('Build started');
infrastructureLogging: {
level: 'verbose'
}
Варианты:
compiler.run((err, stats) => {
const assets =
stats.compilation.assets;
for (const name in assets) {
console.log(name);
}
});
const source =
assets['bundle.js'].source();
compiler.hooks.done.tap(
'ChunksInfo',
stats => {
for (const chunk of stats.compilation.chunks) {
console.log(chunk.name);
}
}
);
Webpack позволяет создавать дочерние компиляторы.
const childCompiler =
compilation.createChildCompiler(
'child',
outputOptions
);
Child compiler используется:
При многократных сборках важно:
Иначе возможны:
Программный API часто используется следующим образом:
CLI
↓
Build Orchestrator
↓
Webpack Compiler
↓
Plugins
↓
Assets
Оркестратор управляет:
| Возможность | CLI | Node API |
|---|---|---|
| Простая сборка | Да | Да |
| Гибкая логика | Ограничено | Полностью |
| Runtime-конфигурация | Частично | Да |
| Интеграция в сервер | Нет | Да |
| Контроль watcher | Ограничено | Да |
| Кастомные пайплайны | Нет | Да |
| Управление памятью | Нет | Да |
| Сценарий | Причина |
|---|---|
| webpack-dev-server | Управление rebuild |
| SSR | Сборка в памяти |
| Electron | Генерация main/renderer bundle |
| Monorepo | Координация сборок |
| IDE | Виртуальные FS |
| Тестовые системы | In-memory compilation |
| CI/CD | Кастомная автоматизация |
| Low-code платформы | Runtime build |
const webpack = require('webpack');
async function compile(config) {
return new Promise((resolve, reject) => {
const compiler = webpack(config);
compiler.run((err, stats) => {
compiler.close(() => {});
if (err) {
reject(err);
return;
}
if (stats.hasErrors()) {
reject(stats.toJson().errors);
return;
}
resolve(stats);
});
});
}
(async () => {
try {
const stats = await compile({
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js'
}
});
console.log(
stats.toString({
colors: true
})
);
} catch (e) {
console.error(e);
}
})();