Программный API Webpack позволяет запускать сборку не через CLI, а
напрямую из JavaScript-кода. Центральной точкой входа является функция
webpack(), принимающая объект конфигурации и возвращающая
экземпляр Compiler либо MultiCompiler.
Такой подход используется в:
Базовый пример:
const webpack = require('webpack');
const config = require('./webpack.config');
const compiler = webpack(config);
После вызова создаётся внутренний граф зависимостей, набор хуков, система плагинов и объект компилятора, управляющий жизненным циклом сборки.
Тип возвращаемого значения зависит от конфигурации.
const compiler = webpack(config);
Возвращается экземпляр Compiler.
const compiler = webpack([
clientConfig,
serverConfig
]);
Возвращается экземпляр MultiCompiler.
Он управляет несколькими независимыми компиляторами одновременно.
Compiler — главный объект Webpack Runtime API.
Он содержит:
Пример:
const webpack = require('webpack');
const compiler = webpack({
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js'
}
});
Метод run() запускает одну полную компиляцию.
compiler.run((err, stats) => {
if (err) {
console.error(err);
return;
}
console.log(stats.toString());
});
Фатальная ошибка уровня компилятора:
compiler.run((err) => {
if (err) {
console.error(err);
}
});
Примеры:
Объект статистики сборки.
Тип:
Stats
Через него доступны:
Ошибки внутри модулей не всегда попадают в err.
Правильная схема:
compiler.run((err, stats) => {
if (err) {
console.error(err);
return;
}
if (stats.hasErrors()) {
console.error(stats.toJson().errors);
}
if (stats.hasWarnings()) {
console.warn(stats.toJson().warnings);
}
});
Это важная особенность API Webpack.
После завершения сборки рекомендуется закрывать compiler:
compiler.run((err, stats) => {
compiler.close((closeErr) => {
console.log('Compiler closed');
});
});
Особенно важно при:
Webpack 5 использует внутренние ресурсы:
Без close() процесс Node.js может не завершаться.
Пример:
compiler.close((err) => {
if (err) {
console.error(err);
}
});
compiler.run((err, stats) => {
const json = stats.toJson();
console.log(json.assets);
});
const info = stats.toJson({
assets: true,
chunks: true,
modules: true
});
console.log(
stats.toString({
colors: true,
modules: false
})
);
Метод watch() запускает постоянное отслеживание
файлов.
compiler.watch({}, (err, stats) => {
console.log('Rebuild completed');
});
compiler.watch({
aggregateTimeout: 300,
poll: 1000
}, callback);
Задержка перед повторной сборкой.
Polling-режим для файловых систем без native watching.
watch() возвращает объект Watching.
const watching = compiler.watch({}, callback);
Через него можно остановить наблюдение:
watching.close(() => {
console.log('Stopped');
});
Принудительный rebuild:
watching.invalidate();
Используется в:
Пример минимального middleware:
const express = require('express');
const webpack = require('webpack');
const app = express();
const compiler = webpack(config);
compiler.watch({}, () => {
console.log('Compiled');
});
app.listen(3000);
Часто программный API применяется вместе с виртуальной файловой системой.
Пример:
const webpack = require('webpack');
const MemoryFS = require('memory-fs');
const compiler = webpack(config);
compiler.outputFileSystem = new MemoryFS();
compiler.run((err, stats) => {
const content =
compiler.outputFileSystem.readFileSync(
'/dist/bundle.js',
'utf8'
);
console.log(content);
});
Webpack абстрагирует файловую систему через интерфейс.
Можно подменять:
compiler.outputFileSystem = customFs;
Используется в:
Можно переопределить и входную файловую систему:
compiler.inputFileSystem = fs;
Очистка внутренних кэшей:
compiler.purgeInputFileSystem();
Полезно при:
Webpack построен вокруг Tapable.
Большая часть API реализована через хуки:
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('Build finished');
});
Инициализация compiler.
compiler.hooks.initialize.tap(
'Plugin',
() => {}
);
Перед запуском сборки.
compiler.hooks.beforeRun.tapAsync(
'Plugin',
(compiler, callback) => {
callback();
}
);
Начало обычной сборки.
compiler.hooks.run.tap(
'Plugin',
() => {}
);
Начало rebuild в watch-режиме.
compiler.hooks.watchRun.tapAsync(
'Plugin',
(compiler, callback) => {
callback();
}
);
Создание Compilation.
compiler.hooks.compile.tap(
'Plugin',
(params) => {}
);
Создание новой compilation.
compiler.hooks.thisCompilation.tap(
'Plugin',
(compilation) => {}
);
Хук новой compilation.
compiler.hooks.compilation.tap(
'Plugin',
(compilation) => {}
);
Перед записью assets.
compiler.hooks.emit.tapAsync(
'Plugin',
(compilation, callback) => {
callback();
}
);
После записи файлов.
compiler.hooks.afterEmit.tap(
'Plugin',
() => {}
);
После завершения сборки.
compiler.hooks.done.tap(
'Plugin',
(stats) => {}
);
Ошибка компиляции.
compiler.hooks.failed.tap(
'Plugin',
(error) => {}
);
Tapable поддерживает:
Пример Promise API:
compiler.hooks.beforeRun.tapPromise(
'Plugin',
async () => {
await doSomething();
}
);
Простейший пример:
class BuildTimePlugin {
apply(compiler) {
compiler.hooks.done.tap(
'BuildTimePlugin',
(stats) => {
console.log(
`Build time: ${stats.endTime - stats.startTime}`
);
}
);
}
}
const compiler = webpack(config);
new BuildTimePlugin().apply(compiler);
Важно различать два объекта.
Глобальный объект процесса сборки.
Живёт между rebuild-ами.
Конкретная единичная компиляция.
Создаётся заново при каждой сборке.
compiler.hooks.compilation.tap(
'Plugin',
(compilation) => {
console.log(compilation.hash);
}
);
Webpack поддерживает дочерние компиляторы.
Пример:
const childCompiler =
compilation.createChildCompiler(
'child',
outputOptions
);
Используется:
При передаче массива конфигураций:
const multiCompiler = webpack([
clientConfig,
serverConfig
]);
multiCompiler.compilers.forEach(
(compiler) => {
console.log(compiler.name);
}
);
multiCompiler.run((err, stats) => {
console.log(stats.stats.length);
});
stats.stats содержит массив статистик.
MultiCompiler может выполнять:
module.exports = [
{
name: 'client'
},
{
name: 'server',
dependencies: ['client']
}
];
Webpack 5 содержит Infrastructure Logging API.
Получение логгера:
const logger =
compiler.getInfrastructureLogger(
'MyPlugin'
);
Использование:
logger.info('Build started');
logger.warn('Potential issue');
logger.error('Build failed');
Настройка:
module.exports = {
infrastructureLogging: {
level: 'verbose'
}
};
Конфигурацию можно модифицировать до запуска:
const compiler = webpack(config);
compiler.options.mode = 'production';
compiler.options.entry = './src/new-entry.js';
compiler.options.output.path =
path.resolve(__dirname, 'build');
Внутри Webpack функция webpack():
Для Node.js Webpack автоматически подключает:
new NodeEnvironmentPlugin({
infrastructureLogging
});
Он внедряет:
В Webpack 5 compiler содержит встроенный cache layer.
Доступ:
compiler.cache
Webpack может переводить кэш в idle state:
compiler.cache.beginIdle();
compiler.cache.endIdle();
compiler.cache.shutdown(callback);
Compiler содержит фабрику резолверов:
compiler.resolverFactory
const resolver =
compiler.resolverFactory.get('normal');
Webpack поддерживает records-файлы.
compiler.readRecords(() => {
console.log('Records loaded');
});
compiler.emitRecords(() => {
console.log('Records saved');
});
После rebuild можно очищать кэш путей:
compiler.purgeInputFileSystem();
Проверка дочернего compiler:
if (compiler.isChild()) {
console.log('Child compiler');
}
Внутренний метод:
const compilation =
compiler.createCompilation();
Используется ядром Webpack.
Создание compilation с хуками:
const compilation =
compiler.newCompilation(params);
Запись assets:
compiler.emitAssets(
compilation,
callback
);
Современный API добавления asset:
compilation.emitAsset(
'file.txt',
new RawSource('content')
);
Удаление asset:
compilation.removeAsset('old.js');
Изменение asset:
compilation.updateAsset(
'bundle.js',
old => transform(old)
);
Полный цикл compiler выглядит следующим образом:
CLI внутри себя также использует webpack().
Разница лишь в:
async function build() {
const compiler = webpack(config);
compiler.run(() => {});
}
compiler.watch({}, callback);
mainCompiler.run(callback);
rendererCompiler.watch({}, callback);
const fs = compiler.outputFileSystem;
compiler.hooks.failed.tap(
'IDE',
showDiagnostics
);
Приводит к зависанию процесса.
Ошибки модулей могут остаться необработанными.
Compiler имеет внутреннее состояние.
Может ломать dependency graph.
Опасно из-за cache invalidation.
const webpack = require('webpack');
async function build(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);
});
});
});
}
Compiler фактически является координатором всей системы
сборки.
Через него проходят:
Именно вокруг Compiler построен весь внутренний runtime
Webpack и большая часть экосистемы плагинов.