С непрерывным развитием фронтенд-инжиниринга инструменты сборки также постоянно совершенствуются. Как новый фаворит эры большого фронтенда, webpack постепенно стал незаменимым инструментом проектирования для инженеров фронтенда в новую эру.Благодаря непрерывной итерации webpack4 мы получаем удовольствие от постоянного улучшения эффективности конструирования и комфорта. непрерывного сокращения конфигурации.Встроенный механизм хуков событий был кропотливо написан, расстроен различными несовместимостями плагинов, хотя процесс и мучителен, но результат всегда прекрасен. После утомительной настройки я часто задаюсь вопросом, а что делает такой аккуратный инструмент в процессе сборки? С таким любопытством я посвящаю себя чтению соответствующих книг и официальных документов и, наконец, понимаю принцип. Теперь давайте постепенно раскроем тайну черного ящика веб-пакета и изучим его механизм работы.
Эта статья будет разделена на три части:Механизм запуска Webpack, написание собственного загрузчика webpack, написание собственного плагина webpackНепосредственно устраните болевые точки принципа веб-пакета, откройте себе дорогу к старшему фронтенд-инженеру~
Эта серия статей о веб-пакете может относиться к проекту:GitHub.com/Jerry только ZR….
Версия веб-пакета, используемая в этой серии статей, — 4. Если у вас есть проблемы с другой версией, вы можете задать вопрос или оставить сообщение в области комментариев под статьей.
1. Механизм работы Webpack
1.1. Обзор механизма работы webpack
Перед прочтением этой статьи я предполагал, что вы освоили базовую настройку вебпака перед компьютером и можете самостоятельно построить систему автоматизированного построения фронтенда на основе вебпака, поэтому эта статья не научит вас настраивать или использовать вебпак, естественные специфические понятия я не буду вводить, я столкнусь с темой и начну объяснять принцип работы вебпака.
Процесс работы webpack можно кратко описать следующим образом:
Инициализировать параметры конфигурации -> привязать обратный вызов обработчика событий -> определить обход входа один за другим -> использовать загрузчик для компиляции файлов -> выходные файлы
Далее мы представим конкретный процесс один за другим.
1.2. запущенный процесс webpack
1.2.1 Предварительное изучение потока событий webpack
При анализе запущенного процесса веб-пакета мы можем использовать концепцию, которая представляет собой механизм потока событий веб-пакета.
Что такое поток событий webpack?
Webpack похож на производственную линию, которая проходит ряд процессов, прежде чем превратить исходные файлы в выходные данные. Ответственность каждого процесса обработки на этой производственной линии едина, и между несколькими процессами существует отношение зависимости.Только после того, как текущая обработка завершена, она может быть передана следующему процессу для обработки. Плагин похож на функцию, встроенную в производственную линию, обрабатывающую ресурсы на производственной линии в определенное время. Webpack использует Tapable для организации этой сложной производственной линии. Webpack будет транслировать события во время выполнения процесса, а плагину нужно только прослушивать те события, о которых он заботится, а затем его можно добавить в производственную линию, чтобы изменить работу производственной линии. Механизм потока событий Webpack обеспечивает упорядоченность плагинов, делая всю систему очень масштабируемой. --Wu Haolin "Объяснение веб-пакета простыми словами"
Мы понимаем поток событий веб-пакета как серию событий в процессе создания веб-пакета.Они представляют различные циклы сборки и состояния.Мы можем прослушивать события в потоке событий так же, как прослушивание события щелчка в браузере, и монтировать их для Обратный звонок по событию. Мы также можем настраивать события и транслировать их в нужное время, используя модули, которые поставляются с веб-пакетом.Tapableудалось. Нам не нужно устанавливать его самостоятельноTapable, он также будет установлен при установке вебпака, если вы хотите его использовать, нам просто нужно прямо в файлеrequireВот и все.
Принцип Tapable на самом деле является EventEmit, с которым мы все сталкиваемся в расширенном интерфейсе. Он реализован через модель издатель-подписчик. Некоторые из его основных кодов можно резюмировать следующим образом:
class SyncHook{
constructor(){
this.hooks = [];
}
// 订阅事件
tap(name, fn){
this.hooks.push(fn);
}
// 发布
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
Конкретное содержание Tapable может относиться к статье:"Таблица анализа исходного кода webpack4.0". Его использование будет подробно описано в модуле «3. Написание собственного подключаемого модуля веб-пакета» далее в этой статье.
Потому что webpack4 переписывает механизм потоковой передачи событий, так что если мы пролистаемwebpack hookв официальной документации эта информация покажется особенно сложной, но в реальном использовании нам нужно помнить только несколько важных событий.
1.2.2 Подробное объяснение процесса запуска вебпака
Прежде чем объяснять процесс веб-пакета, я прикреплю нарисованную мной блок-схему выполнения:
- Во-первых, webpack прочитает конфигурацию, которую вы передаете в командной строке, и проект
webpack.config.jsфайл, инициализируйте параметры конфигурации этой сборки и выполните оператор создания экземпляра плагина в файле конфигурации, сгенерируйте метод применения, переданный в плагин компилятором, и перехватите пользовательский хук для потока событий веб-пакета. - После этапа entryOption веб-пакет начинает читать настроенные записи и рекурсивно проходит все файлы записей.
- Затем Webpack запускает процесс компиляции. Он будет входить в каждый входной файл (entry) по очереди, сначала используем настроенный пользователем загрузчик для компиляции содержимого файла (buildModule), мы можем получить ресурс модуля (путь к ресурсу), загрузчики (После этого используем acorn для разбора файла содержимое скомпилированного файла для создания статического синтаксического дерева AST (normalModuleLoader), анализа зависимостей файла, извлечения зависимых модулей один за другим и повторения вышеуказанного процесса, и, наконец, помещения всех модулей в
requireзаменить синтаксис на__webpack_require__для имитации модульной работы. - На этапе создания завершены компиляция и преобразование всех файлов, включая окончательные выходные ресурсы.
compilation.assetsПолучить необходимые данные, в том числе информацию о ресурсах для вывода, блоках кода Chunk и т.д.
1.2.3 Что такое АСТ?
В 1.2.2 увидели незнакомое слово - AST, поищите в интернете:
В информатике абстрактное синтаксическое дерево (AST) или просто синтаксическое дерево — это абстрактное представление грамматической структуры исходного кода. Он представляет синтаксическую структуру языка программирования в виде дерева, и каждый узел в дереве представляет собой структуру в исходном коде. Грамматика называется «абстрактной», потому что грамматика здесь не представляет все детали, которые встречаются в реальной грамматике. Например, вложенные круглые скобки неявно присутствуют в древовидной структуре и не представлены в виде узлов, а операторы условного перехода, такие как if-condition-then, могут быть представлены узлами с двумя ветвями. --Википедия
На самом деле вам просто нужно помнить, что AST — это дерево, например:
Цель преобразования в AST состоит в том, чтобы преобразовать написанный нами строковый файл в структуру данных, более легко распознаваемую компьютером, чтобы в ней было легче извлекать ключевую информацию, а представление этого дерева на компьютере было на самом деле простой объект.
Пример представляет собой простую декларативную инструкцию присваивания.После преобразования AST значение каждой части становится более понятным.
1.2.4 Анализ результатов вывода webpack
Далее давайте посмотрим на вывод webpack. Если мы не установим splitChunk, мы увидим только выходной файл main.js в каталоге dist, отфильтруем бесполезные комментарии и некоторые функции, которые не нужно учитывать в настоящее время, результирующий код, вероятно, будет следующим:
(function (modules) {
// 缓存已经加载过的module的exports
// module在exports之前还是有js需要执行的,缓存的目的就是优化这一过程
// The module cache
var installedModules = {};
// The require function
/**
* 模拟CommonJS require()
* @param {String} moduleId 模块路径
*/
function __webpack_require__(moduleId) {
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// 执行单个module JS Function并填充installedModules与module
// function mudule(module, __webpack_exports__[, __webpack_require__])
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
......
// __webpack_public_path__
__webpack_require__.p = "";
// 加载Entry并返回Entry的exports
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
// modules其实就是一个对象,键是模块的路径,值就是模块的JS Function
({
"./src/index.js": function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./module.js */ \"./src/module.js\");\n/* harmony import */ var _module_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_module_js__WEBPACK_IMPORTED_MODULE_0__);\n{};\nconsole.log(_module_js__WEBPACK_IMPORTED_MODULE_0___default.a.s);\n\n//# sourceURL=webpack:///./src/index.js?");
},
"./src/module.js": function (module, exports) {
eval("{};var s = 123;\nconsole.log(s);\nmodule.exports = {\n s: s\n};\n\n//# sourceURL=webpack:///./src/module.js?");
}
});
Мы все знаем, что суть модуляризации веб-пакета в браузере заключается во внедрении всего кода в один и тот же файл JS. Теперь мы можем ясно видеть, что окончательный сгенерированный веб-пакет — это просто IIFE. функция и собирается в объект, который передается в качестве аргумента IIFE.
Но если мы настроим splitChunk, выходной файл будет привязан к вашему Chunk, и код изменится:
//@file: dist/common/runtime.js
// 当配置了splitChunk之后,此时IIFE的形参modules就成了摆设,
// 真正的module还有chunk都被存放在了一个挂载在window上的全局数组`webpackJsonp`上了
(function(modules) { // webpackBootstrap
// install a JSONP callback for chunk loading
/**
* webpackJsonpCallback 处理chunk数据
* @param {Array} data [[chunkId(chunk名称)], modules(Object), [...other chunks(所有需要的chunk)]]
*/
function webpackJsonpCallback(data) {
// chunk的名称,如果是entry chunk也就是我们entry的key
var chunkIds = data[0];
// 依赖模块
var moreModules = data[1];
var executeModules = data[2];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [];
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) {
resolves.shift()();
}
// add entry modules from loaded chunk to deferred list
deferredModules.push.apply(deferredModules, executeModules || []);
// run deferred modules when all chunks ready
return checkDeferredModules();
};
function checkDeferredModules() {
var result;
for(var i = 0; i < deferredModules.length; i++) {
var deferredModule = deferredModules[i];
var fulfilled = true;
for(var j = 1; j < deferredModule.length; j++) {
var depId = deferredModule[j];
if(installedChunks[depId] !== 0) fulfilled = false;
}
if(fulfilled) {
deferredModules.splice(i--, 1);
result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
}
}
return result;
}
// The module cache
var installedModules = {};
// 缓存chunk,同理module
// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
var installedChunks = {
"common/runtime": 0
};
var deferredModules = [];
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
......
// __webpack_public_path__
__webpack_require__.p = "";
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
// run deferred modules from other chunks
checkDeferredModules();
})([]);
//@file: dist/common/utils.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["common/utils"], {
"./src/index.js": function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./module.js */ \"./src/module.js\");\n/* harmony import */ var _module_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_module_js__WEBPACK_IMPORTED_MODULE_0__);\n{};\nconsole.log(_module_js__WEBPACK_IMPORTED_MODULE_0___default.a.s);\n\n//# sourceURL=webpack:///./src/index.js?");
},
"./src/module.js": function (module, exports) {
eval("{};var s = 123;\nconsole.log(s);\nmodule.exports = {\n s: s\n};\n\n//# sourceURL=webpack:///./src/module.js?");
}
}]);
На этот раз формальные параметры IIFE также стали украшением, а все наши модули размещены в файле с именемwebpackJsonpна глобальном массиве , через IIFEwebpackJsonpCallbackдля обработки данных.
1.3. Резюме
Глядя на процесс построения веб-пакета, мы можем обнаружить, что основная трудоемкая часть всего процесса построения — это процесс рекурсивного обхода каждой записи, а затем поиск зависимостей для компиляции одна за другой.String->AST->StringЗагрузчик также должен обрабатывать некоторые строки или выполнять некоторые сценарии JS.Из-за однопоточного барьера node.js медленная сборка веб-пакета всегда была причиной его критики. Это также причина, почему happypack так популярен, мы можем посмотреть пример кода happypack:
// @file: webpack.config.js
const HappyPack = require('happypack');
const os = require('os');
// 开辟一个线程池
// 拿到系统CPU的最大核数,让happypack将编译工作灌满所有CPU核
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
// ...
plugins: [
new HappyPack({
id: 'js',
threadPool: happyThreadPool,
loaders: [ 'babel-loader' ]
}),
new HappyPack({
id: 'styles',
threadPool: happyThreadPool,
loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
})
]
};
Если вы использовали pm2, вы можете легко понять это.На самом деле принцип тот же.Все они используют нативный кластерный модуль node.js для открытия многопроцессорной конструкции выполнения, но после 4 у вас нет чтобы беспокоиться об этой проблеме.Теперь многопроцессные сборки были интегрированы в сам webpack.Помимо инкрементной компиляции, это одна из причин, почему 4 может значительно повысить эффективность сборки.
2. Напишите собственный загрузчик веб-пакетов
2.1. Сделайте прототип загрузчика webpack
В webpack именно наш загрузчик играет роль компилятора, то есть мы обычно делаем компиляцию ES6 для babel, SCSS, LESS и другие компиляции все делаются в загрузчике, прежде чем вы узнаете суть загрузчика, вы must Вы почувствуете, что это очень высокоуровневая вещь, как и принцип компиляции в информатике, в нем должно быть много сложных операций. Но на самом деле загрузчик — это обычная функция, она будет передавать совпадающее содержимое файла (String), вам просто нужно выполнить некоторую обработку этих строк. Простой загрузчик может выглядеть так:
/**
* loader Function
* @param {String} content 文件内容
*/
module.exports = function(content){
return "{};" + content
}
как его использовать иbabel-loaderто же самое, только нужноwebpack.config.jsизmodule.rulesПросто добавьте в массив такой объект:
{
test: /\.js$/,
exclude: /node_modules/,
use: {
//这里是我的自定义loader的存放路径
loader: path.resolve('./loaders/index.js'),
options: {
test: 1
}
}
}
Таким образом, загрузчик будет соответствовать всем.jsфайл заканчивается суффиксом и добавляется перед содержимым{};Такой кусок кода, мы можем увидеть эффект в выходном файле:
Итак, как только вы получите содержимое файла, как вы хотите поступить со строкой, зависит от вас ~ вы можете импортировать ее.babelКугаbabel(content), так что компиляция реализована, а так же может быть введенаuglifyjsСтроковое сжатие содержимого файла, всю работу определяете вы.
2.2.Обычные навыки боя заряжающих
2.2.1 Получить пользовательскую конфигурацию загрузчика
пока мыwebpack.config.jsПри написании конфигурации загрузчика часто можно увидетьoptionsТакой элемент конфигурации является пользовательской конфигурацией, предоставляемой веб-пакетом для пользователей.В нашем загрузчике, если мы хотим получить такую информацию о конфигурации, нам нужно использовать только эту упакованную библиотекуloader-utilsПросто хорошо:
const loaderUtils = require("loader-utils");
module.exports = function(content){
// 获取用户配置的options
const options = loaderUtils.getOptions(this);
console.log('***options***', options)
return "{};" + content
}
2.2.2 Форма экспортируемых загрузчиком данных
В предыдущем примере, поскольку наш загрузчик всегда является функцией, мы используем метод возврата для экспорта данных, обработанных загрузчиком, но это не самый рекомендуемый способ записи.В большинстве случаев мы по-прежнему предпочитаем использоватьthis.callbackспособ экспорта данных. Если это написано таким образом, пример кода можно переписать как:
module.exports = function(content){
//return "{};" + content
this.callback(null, "{};" + content)
}
this.callbackМожно передать четыре параметра (последние два параметра можно опустить), это:
- error: Error | null, при сбое загрузчика выкидывает Error
- content: String | Buffer, содержимое, которое необходимо экспортировать после компиляции загрузчиком
- sourceMap: исходная карта скомпилированного контента, сгенерированная для отладки.
- ast: статическое синтаксическое дерево AST, сгенерированное этой компиляцией, загрузчик, выполняемый позже, может напрямую использовать этот AST, что может сэкономить процесс многократного создания AST.
2.2.3 Асинхронный загрузчик
После 2.2.2 мы можем обнаружить, что при использованииreturnвсе ещеthis.callbackИсполнение результатов экспорта синхронное, что делать, если в нашем загрузчике есть асинхронные операции, такие как пулл-реквесты и т.п.?
Друзья, знакомые с ES6, знают, что самое простое решение — инкапсулироватьPromise, затем используйтеasync-awaitПолностью игнорируя асинхронную проблему, пример кода выглядит следующим образом:
module.exports = async function(content){
function timeout(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("{};" + content)
}, delay)
})
}
const data = await timeout(1000)
return data
}
Но если версии узла недостаточно, у нас остается исходный почвенный раствор.this.async, вызов этого метода вернет функцию обратного вызова, которая может быть выполнена в соответствующее время.Приведенный выше пример кода можно переписать как:
module.exports = function(content){
function timeout(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("{};" + content)
}, delay)
})
}
const callback = this.async()
timeout(1000).then(data => {
callback(null, data)
})
}
Старые версии узла делают то же самое.
2.2.4 Порядок выполнения загрузчиков
Помните загрузчики, которые мы писали при настройке компиляции CSS, они выглядят так:
Во многих случаях нашаuseСуществует не только один загрузчик, порядок выполнения этих загрузчиков сзади вперед, вы также можете понимать это как процесс укладки массива загрузчиков.
2.2.5.кэш загрузчика
Механизм инкрементной компиляции webpack будет наблюдать за измененными файлами при каждой компиляции.По умолчанию webpack будет кешировать результаты выполнения загрузчика, что может сильно повысить скорость сборки, но мы также можем отключить его вручную (хотя я не знаю Почему вы хотите его закрыть? Раз вы оставили такой API, я представлю его. Добро пожаловать, чтобы добавить), пример кода выглядит следующим образом:
module.exports = function(content){
//关闭loader缓存
this.cacheable(false);
return "{};" + content
}
2.2.6. Перехватчик питча для передачи параметров во всем процессе
В файле загрузчика вы можете экспортировать файл с именемpitchфункция, она будет выполняться перед всеми загрузчиками, вот так:
module.exports.pitch = (remaining, preceding, data) => {
console.log('***remaining***', remaining)
console.log('***preceding***', preceding)
// data会被挂在到当前loader的上下文this上在loaders之间传递
data.value = "test"
}
Он может принимать три параметра, самый главный это данные третьего параметра, на него можно повесить какие-то требуемые значения, и все загрузчики в правиле могут получить это значение во время выполнения.
module.exports = function(content){
//***this data*** test
console.log('***this data***', this.data.value)
return "{};" + content
}
module.exports.pitch = (remaining, preceding, data) => {
data.value = "test"
}
2.3. Резюме
Из приведенного выше введения мы понимаем, что загрузчик на самом деле является «непримечательной» функцией, которая может передавать содержимое файла, совпадающего на этот раз, для нас, чтобы настроить и изменить.
3. Напишите собственный плагин веб-пакета
3.1. Просмотрите поток событий веб-пакета
Помните поток событий веб-пакета, о котором мы говорили ранее, помните ли вы, какие общие события есть в веб-пакете? Роль подключаемого модуля веб-пакета состоит в том, чтобы монтировать обратные вызовы для этих событий или выполнять указанные сценарии.
Мы также упоминали в статье, что поток событий webpack проходит черезTapableОн реализован так же, как и наш EventEmit, это инструмент генерации и управления этой серией событий, и некоторые из его основных кодов таковы:
class SyncHook{
constructor(){
this.hooks = [];
}
// 订阅事件
tap(name, fn){
this.hooks.push(fn);
}
// 发布
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
существуетwebpack hookВсе хуки являются примерами Tapable , так что мы можем пройтиtapметод прослушивания событий, используйтеcallМетод транслирует события, как описано в официальной документации:
compiler.hooks.someHook.tap(/* ... */);
Мы уже представили несколько наиболее часто используемых хуков в предыдущей статье. Если вы их не помните, вы можете вернуться и посмотреть на них~
3.2 Что такое плагин веб-пакета
Если разобрать суть плагина webpack, то он на самом деле так же прост, как и загрузчик webpack, по сути, это просто класс с методом apply.
//@file: plugins/myplugin.js
class myPlugin {
constructor(options){
//用户自定义配置
this.options = options
console.log(this.options)
}
apply(compiler) {
console.log("This is my first plugin.")
}
}
module.exports = myPlugin
Это реализует простой плагин веб-пакета, если мы хотим его использовать, нам нужно толькоwebpack.config.jsвнутриrequireи создайте его просто отлично:
const MyPlugin = require('./plugins/myplugin-4.js')
module.exports = {
......,
plugins: [
new MyPlugin("Plugin is instancing.")
]
}
Все, должно быть, вспомнили это сейчас. Каждый раз, когда нам нужно использовать плагин, нам нужно создавать его экземпляр с помощью new. Естественно, параметры, переданные в процессе экземпляра, становятся параметрами, которые мы получаем в конструкторе.
Время создания всех плагинов наступает, когда webpack инициализирует все параметры, то есть когда начинается поток событий. Итак, если подходятshell.jsВ ожидании библиотеки инструментов мы можем выполнять операции с файлами и другие связанные скрипты в это время, что и делает плагин webpack.
Если вы хотите выполнить какой-то скрипт в указанное время, то естественно можете использовать метод монтирования коллбэка на поток событий webpack, и выполнять нужную вам операцию в колбэке.
3.3 Новое использование Tapable
Если мы хотим предоставить поток событий webpack для наших пользовательских событий, можно ли это сделать?
Ответ, конечно, в том, что это должно быть возможно, старое железо!
Сколько шагов требуется для настройки событий потока событий webpack? Четыре шага:
-
Представьте Tapable и найдите крючок, который хотите использовать,Синхронизирующий хук or асинхронный хукВсе здесь ->Tapable анализа исходного кода webpack4.0
const { SyncHook } = require("tapable"); -
Создайте нужные вам хуки в Tapable и смонтируйте их в компиляторе или компиляции.
compiler.hooks.myHook = new SyncHook(['data']) -
коснитесь слушателя, где вам нужно слушать события
compiler.hooks.myHook.tap('Listen4Myplugin', (data) => { console.log('@Listen4Myplugin', data) }) -
Выполните метод вызова и передайте данные, когда вам нужно транслировать событие
compiler.hooks.environment.tap(pluginName, () => { //广播自定义事件 compiler.hooks.myHook.call("It's my plugin.") });
Полная реализация кода может относиться к проекту, который я разместил в начале статьи, который, вероятно, выглядит следующим образом:
Теперь я создаю хук в своем собственном плагине и монтирую его в поток событий веб-пакета.
// @file: plugins/myplugin.js
const pluginName = 'MyPlugin'
// tapable是webpack自带的package,是webpack的核心实现
// 不需要单独install,可以在安装过webpack的项目里直接require
// 拿到一个同步hook类
const { SyncHook } = require("tapable");
class MyPlugin {
// 传入webpack config中的plugin配置参数
constructor(options) {
// { test: 1 }
console.log('@plugin constructor', options);
}
apply(compiler) {
console.log('@plugin apply');
// 实例化自定义事件
compiler.hooks.myPlugin = new SyncHook(['data'])
compiler.hooks.environment.tap(pluginName, () => {
//广播自定义事件
compiler.hooks.myPlugin.call("It's my plugin.")
console.log('@environment');
});
// compiler.hooks.compilation.tap(pluginName, (compilation) => {
// 你也可以在compilation上挂载hook
// compilation.hooks.myPlugin = new SyncHook(['data'])
// compilation.hooks.myPlugin.call("It's my plugin.")
// });
}
}
module.exports = MyPlugin
Слушайте мое пользовательское событие в плагине прослушивателя
// @file: plugins/listen4myplugin.js
class Listen4Myplugin {
apply(compiler) {
// 在myplugin environment 阶段被广播
compiler.hooks.myPlugin.tap('Listen4Myplugin', (data) => {
console.log('@Listen4Myplugin', data)
})
}
}
module.exports = Listen4Myplugin
Введите два плагина и создайте их экземпляры в конфигурации веб-пакета.
// @file: webpack.config.js
const MyPlugin = require('./plugins/myplugin-4.js')
const Listen4Myplugin = require('./plugins/listen4myplugin.js')
module.exports = {
......,
plugins: [
new MyPlugin("Plugin is instancing."),
new Listen4Myplugin()
]
}
Вывод таков:
Мы получили данные, переданные методом call, и успешно вывели их в соответствии со временем среды.
3.4 Анализ реального боя
Давайте посмотрим, во что играли всеhtml-webpack-plugin, мы обнаружили, что внизу ридми есть вот такое демо:
function MyPlugin(options) {
// Configure your plugin with options...
}
MyPlugin.prototype.apply = function (compiler) {
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
console.log('The compiler is starting a new compilation...');
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync(
'MyPlugin',
(data, cb) => {
data.html += 'The Magic Footer'
cb(null, data)
}
)
})
}
module.exports = MyPlugin
Если вы внимательно прочитаете содержание предыдущего раздела, то обнаружите, что этоhtmlWebpackPluginAfterHtmlProcessingРазве это не пользовательское событие, когда этот плагин монтирует себя в потоке событий веб-пакета, он вызывает ваш пользовательский обратный вызов, когда выходной файл сгенерирован и готов к внедрению в HTML, и передает файл ресурсов, сгенерированный после этой компиляции, в обратный вызов , Соответствующая информация и содержимое HTML-файла, которые необходимо ввести (в виде строки) для нашей пользовательской операции. Ищите этот хук в проекте:
Это не то же самое, что мы говорили в 3.2.Сначала создайте экземпляры нужных нам хуков.Из названия видно, что только первый является синхронным хуком, а остальные асинхронными хуками. Тогда ищите трансляцию мероприятия:
Это точно так же, как то, что мы только что представили, за исключением того, что асинхронный хук использует метод обещания для трансляции, а остальное не является полностью процессом нашего пользовательского события. Если вам интересно, вы можете перейти в консоль, чтобы увидетьhtmlWebpackPluginAfterHtmlProcessingДанные, переданные этим хуком обратному вызову, возможно, вы сможете открыть новый континент.
жесткий широкий
Наша команда набирает сотрудников! ! ! Добро пожаловать в команду внешнего интерфейса ByteDance для монетизации бизнеса.Техническое строительство, которое мы делаем, включает в себя: обновление системы внешнего интерфейса, построение инфраструктуры командного узла, инструменты для публикации CI одним щелчком мыши, поддержку обслуживания компонентов, интернационализированный интерфейс. Общие решения, сверхмощные Опираясь на трансформацию микроинтерфейса бизнес-системы, систему визуального построения страниц, систему бизнес-аналитики BI, автоматическое тестирование интерфейса и т. д., команда разработчиков интерфейса Пекина, Шанхая и Ханчжоу 100 человек точно найдут для вас области интереса. Если вы хотите присоединиться к нам, добро пожаловать, нажмите на мой внутренний push-канал:
✨✨✨✨✨
Пуш-портал (Золотой сезон найма, нажмите, чтобы получить возможности внутреннего продвижения ByteDance!)
Эксклюзивный вход для набора в школу (код ByteDance для набора в школу: HTZYCHN, ссылка для доставки:Присоединяйтесь к ByteDance-Рекрутмент)
✨✨✨✨✨
Если вы хотите узнать о повседневной жизни (ду), жизни (би) и рабочей среде (ли) нашего отдела, вы также можетекликните сюдаЯ понимаю~