Webpack делает его более гибким благодаря механизму плагинов для адаптации к различным сценариям приложений. В течение жизненного цикла работы Webpack многие события транслируются, и плагин может прослушивать эти события и изменять выходные результаты через API, предоставляемый Webpack, в соответствующее время.
Базовый код плагина выглядит следующим образом:
class BasicPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options){
}
// Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler){
compiler.plugin('compilation',function(compilation) {
})
}
}
// 导出 Plugin
module.exports = BasicPlugin;
При использовании этого плагина соответствующий код конфигурации выглядит следующим образом:
const BasicPlugin = require('./BasicPlugin.js');
module.export = {
plugins:[
new BasicPlugin(options),
]
}
После запуска Webpack он будет выполняться первым в процессе чтения конфигурацииnew BasicPlugin(options)
Инициализируйте BasicPlugin, чтобы получить его экземпляр.
После инициализации объекта компилятора вызовитеbasicPlugin.apply(compiler)
Передайте объект компилятора экземпляру плагина.
После того, как экземпляр плагина получит объект компилятора, он может передатьcompiler.plugin(事件名称, 回调函数)
Слушайте события, транслируемые Webpack.
И вы можете управлять Webpack через объект компилятора.
Я полагаю, что с помощью простейшего плагина выше вы, вероятно, понимаете принцип работы плагина, но есть еще много деталей, на которые следует обратить внимание в фактической разработке, которые будут подробно представлены ниже.
Компилятор и компиляция
Двумя наиболее часто используемыми объектами при разработке плагина являются Компилятор и Компиляция, которые являются связующим звеном между Плагином и Webpack. Значения компилятора и компиляции следующие:
- Объект Compiler содержит всю информацию о конфигурации среды Webpack, включая параметры, загрузчики и подключаемые модули. Этот объект создается при запуске Webpack. Он уникален в глобальном масштабе и может быть просто понят как экземпляр Webpack;
- Объект Compilation содержит текущие ресурсы модуля, скомпилированные ресурсы, измененные файлы и т. д. Когда Webpack работает в режиме разработки, каждый раз, когда обнаруживается изменение файла, будет создаваться новая компиляция. Объект Compilation также предоставляет множество обратных вызовов событий для расширений плагинов. Объекты компилятора также можно прочитать с помощью Compilation.
Разница между компилятором и компиляцией заключается в том, что компилятор представляет собой весь жизненный цикл Webpack от запуска до завершения работы, а компиляция просто представляет собой новую компиляцию.
поток событий
Webpack подобен производственной линии, проходящей ряд процессов для преобразования исходных файлов в выходные данные. Ответственность каждого процесса обработки на этой производственной линии едина, и между несколькими процессами существует отношение зависимости.Только после завершения текущей обработки он может быть передан следующему процессу для обработки. Плагин похож на функцию, встроенную в производственную линию, обрабатывающую ресурсы на производственной линии в определенное время.
Вебпак черезTapableОрганизовать эту сложную производственную линию. Webpack во время работы будет транслировать событие, просто подключите его, чтобы прослушать интересующие события, можно добавить в эту производственную линию, чтобы изменить работу производственной линии. Механизм потока событий Webpack, гарантирующий, что упорядоченный виджет обеспечивает хорошую масштабируемость всей системы.
Механизм потоковой передачи событий Webpack применяет шаблон наблюдателя, который очень похож на EventEmitter в Node.js. И компилятор, и компиляция наследуются от Tapable и могут транслировать и прослушивать события непосредственно на объектах компилятора и компиляции следующим образом:
/**
* 广播出事件
* event-name 为事件名称,注意不要和现有的事件重名
* params 为附带的参数
*/
compiler.apply('event-name',params);
/**
* 监听名称为 event-name 的事件,当 event-name 事件发生时,函数就会被执行。
* 同时函数中的 params 参数为广播事件时附带的参数。
*/
compiler.plugin('event-name',function(params) {
});
Подобным образом компиляция.apply и компиляция.плагин используются так же, как и выше.
При разработке плагина вы можете не знать, с чего начать, потому что не знаете, какое событие нужно прослушивать, чтобы выполнить работу.
При разработке плагина необходимо обратить внимание на следующие два момента:
- Пока вы можете получить компилятор или объект компиляции, вы можете транслировать новые события, поэтому вы также можете транслировать события в недавно разработанных плагинах для других плагинов для мониторинга и использования.
- Объекты Compiler и Compilation, передаваемые каждому подключаемому модулю, являются одной и той же ссылкой. Другими словами, изменение свойств компилятора или объекта компиляции в одном плагине повлияет на последующие плагины.
- Некоторые события являются асинхронными. Эти асинхронные события будут иметь два параметра. Второй параметр — это функция обратного вызова. Когда плагин завершает обработку задачи, ему необходимо вызвать функцию обратного вызова, чтобы уведомить Webpack перед переходом к следующему процессу обработки. Например:
compiler.plugin('emit',function(compilation, callback) { // 支持处理逻辑 // 处理完毕后执行 callback 以通知 Webpack // 如果不执行 callback,运行流程将会一直卡在这不往下执行 callback(); });
Общий API
Плагины можно использовать для изменения выходных файлов, добавления выходных файлов и даже повышения производительности Webpack и т. д. Короче говоря, плагины могут выполнять множество задач, вызывая API, предоставляемый Webpack. Поскольку Webpack предоставляет множество API, есть много API, которые используются редко, а место ограничено.Вот некоторые часто используемые API.
Чтение выходных ресурсов, блоков кода, модулей и их зависимостей
Некоторым плагинам может потребоваться прочитать результаты обработки Webpack, такие как выходные ресурсы, блоки кода, модули и их зависимости, для дальнейшей обработки.
существуетemit
Когда событие происходит, это означает, что преобразование и сборка исходного файла завершены, и здесь вы можете прочитать окончательный выходной ресурс, блок кода, модуль и его зависимости, а также изменить содержимое выходного ресурса.
Код плагина следующий:
class Plugin {
apply(compiler) {
compiler.plugin('emit', function (compilation, callback) {
// compilation.chunks 存放所有代码块,是一个数组
compilation.chunks.forEach(function (chunk) {
// chunk 代表一个代码块
// 代码块由多个模块组成,通过 chunk.forEachModule 能读取组成代码块的每个模块
chunk.forEachModule(function (module) {
// module 代表一个模块
// module.fileDependencies 存放当前模块的所有依赖的文件路径,是一个数组
module.fileDependencies.forEach(function (filepath) {
});
});
// Webpack 会根据 Chunk 去生成输出的文件资源,每个 Chunk 都对应一个及其以上的输出文件
// 例如在 Chunk 中包含了 CSS 模块并且使用了 ExtractTextPlugin 时,
// 该 Chunk 就会生成 .js 和 .css 两个文件
chunk.files.forEach(function (filename) {
// compilation.assets 存放当前所有即将输出的资源
// 调用一个输出资源的 source() 方法能获取到输出资源的内容
let source = compilation.assets[filename].source();
});
});
// 这是一个异步事件,要记得调用 callback 通知 Webpack 本次事件监听处理结束。
// 如果忘记了调用 callback,Webpack 将一直卡在这里而不会往后执行。
callback();
})
}
}
Отслеживание изменений файлов
существует4-5 Использовать автообновлениеКак представлено в Webpack, он запускается с настроенного модуля ввода и по очереди находит все зависимые модули.Когда модуль ввода или его зависимые модули изменяются, Будет запущена новая компиляция.
При разработке плагина часто необходимо знать, какой файл был изменен, что привело к новой компиляции, для этого можно использовать следующий код:
// 当依赖的文件发生变化时会触发 watch-run 事件
compiler.plugin('watch-run', (watching, callback) => {
// 获取发生变化的文件列表
const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
// changedFiles 格式为键值对,键为发生变化的文件路径。
if (changedFiles[filePath] !== undefined) {
// filePath 对应的文件发生了变化
}
callback();
});
По умолчанию Webpack отслеживает только изменение записи и зависимых от нее модулей.В некоторых случаях проекту может потребоваться добавить новые файлы, например HTML-файл. Поскольку файлы JavaScript не импортируют файлы HTML, Webpack не будет прослушивать изменения в файлах HTML и не будет повторно запускать новую компиляцию при редактировании файлов HTML. Чтобы отслеживать изменения в файле HTML, нам нужно добавить файл HTML в список зависимостей, для этого мы можем использовать следующий код:
compiler.plugin('after-compile', (compilation, callback) => {
// 把 HTML 文件添加到文件依赖列表,好让 Webpack 去监听 HTML 模块文件,在 HTML 模版文件发生变化时重新启动一次编译
compilation.fileDependencies.push(filePath);
callback();
});
Изменить выходной ресурс
В некоторых сценариях подключаемым модулям необходимо изменять, добавлять и удалять выходные ресурсы.Для этого необходимо отслеживатьemit
событие, потому чтоemit
И конвертирование кода событий, соответствующие всем модулям, создаст файл,
Ресурсы требуемые выходные данные о выходе,emit
События — это последний шанс изменить выходные ресурсы Webpack.
Все ресурсы, которые необходимо вывести, будут храниться вcompilation.assets
середина,compilation.assets
Это пара ключ-значение, ключ — это имя файла для вывода, а значение — это содержимое, соответствующее файлу.
настраиватьcompilation.assets
Код выглядит следующим образом:
compiler.plugin('emit', (compilation, callback) => {
// 设置名称为 fileName 的输出资源
compilation.assets[fileName] = {
// 返回文件内容
source: () => {
// fileContent 既可以是代表文本文件的字符串,也可以是代表二进制文件的 Buffer
return fileContent;
},
// 返回文件大小
size: () => {
return Buffer.byteLength(fileContent, 'utf8');
}
};
callback();
});
читатьcompilation.assets
Код выглядит следующим образом:
compiler.plugin('emit', (compilation, callback) => {
// 读取名称为 fileName 的输出资源
const asset = compilation.assets[fileName];
// 获取输出资源的内容
asset.source();
// 获取输出资源的文件大小
asset.size();
callback();
});
Определите, какие плагины использует Webpack
При разработке плагина вам может потребоваться принять следующее решение на основе того, использует ли текущая конфигурация другой плагин, поэтому вам нужно прочитать текущую конфигурацию плагина Webpack. Чтобы судить, используется ли в настоящее время ExtractTextPlugin в качестве примера, вы можете использовать следующий код:
// 判断当前配置使用使用了 ExtractTextPlugin,
// compiler 参数即为 Webpack 在 apply(compiler) 中传入的参数
function hasExtractTextPlugin(compiler) {
// 当前配置所有使用的插件列表
const plugins = compiler.options.plugins;
// 去 plugins 中寻找有没有 ExtractTextPlugin 的实例
return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null;
}
настоящий бой
Давайте возьмем практический пример и шаг за шагом проведем вас по реализации плагина.
Плагин называется EndWebpackPlugin, и его функция заключается в добавлении некоторых дополнительных операций перед выходом Webpack, таких как выполнение операции публикации и загрузка выходного файла на сервер после того, как Webpack успешно скомпилирует и выведет файл. В то же время плагин также может определить, успешно ли выполнена сборка Webpack. Вот как использовать плагин:
module.exports = {
plugins:[
// 在初始化 EndWebpackPlugin 时传入了两个参数,分别是在成功时的回调函数和失败时的回调函数;
new EndWebpackPlugin(() => {
// Webpack 构建成功,并且文件输出了后会执行到这里,在这里可以做发布文件操作
}, (err) => {
// Webpack 构建失败,err 是导致错误的原因
console.error(err);
})
]
}
Для реализации этого плагина необходимы два события:
- done: Происходит, когда Webpack собирается выйти после успешной сборки и вывода файлов;
- failed: Происходит, когда сборка завершается сбоем из-за исключения, и Webpack вот-вот выйдет;
Реализовать этот плагин очень просто, полный код выглядит следующим образом:
class EndWebpackPlugin {
constructor(doneCallback, failCallback) {
// 存下在构造函数中传入的回调函数
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler) {
compiler.plugin('done', (stats) => {
// 在 done 事件中回调 doneCallback
this.doneCallback(stats);
});
compiler.plugin('failed', (err) => {
// 在 failed 事件中回调 failCallback
this.failCallback(err);
});
}
}
// 导出插件
module.exports = EndWebpackPlugin;
Из разработки этого плагина видно, что особенно важно найти соответствующую точку события для завершения функции при разработке плагина. существует5-1 Краткое изложение принципа работыВ частности, Webpack транслирует общие события во время запущенного процесса, и вы можете найти нужные вам события.
этот примерПредоставьте полный код проекта