В этой статье используется исходный код WebPack-Dev-Server, реализуется HMR горячего обновления WebPack с нуля, глубоко понимается механизм реализации WebPack-Dev-Server, WebPack-Middleware и полностью понимается их принципы. интервью очень беглое, эту часть можно носить перед созданием процесса строительных лесов. Зная это, зная это, даже следующий уровень.
Теплое напоминание ❤️~ Длина большая, рекомендуется сохранить на компьютер для лучшего использования.
Горячая замена модуля означает, что когда мы изменяем и сохраняем код, веб-пакет переупаковывает код и отправляет новый модуль в браузер.Браузер заменяет старый модуль новым модулем для достижения обновления страницы на основе обновления браузера.
2. Преимущества
относительноlive reloadСхема обновления страницы, преимущество HMR в том, что он может сохранять состояние приложения и повышать эффективность разработки
3. Тогда используйте его
./src/index.js
// 创建一个input,可以在里面输入一些东西,方便我们观察热更新的效果
let inputEl = document.createElement("input");
document.body.appendChild(inputEl);
let divEl = document.createElement("div")
document.body.appendChild(divEl);
let render = () => {
let content = require("./content").default;
divEl.innerText = content;
}
render();
// 要实现热更新,这段代码并不可少,描述当模块被更新后做什么
// 为什么vue-cli中.vue不用写额外的逻辑,也可以实现热更新呢?那是因为有vue-loader帮我们做了,很多loader都实现了热更新
if (module.hot) {
module.hot.accept(["./content.js"], render);
}
./src/content.js
let content = "hello world"
console.log("welcome");
export default content;
cd 项目根目录
npm run dev
4. Эффект изображения
Когда мы введем 123 в поле ввода и обновим код в content.js в это время, мы обнаружим, что hello world!!!! становится hello world, но значение поля ввода все еще сохраняется, что и является значением HMR, сохранение состояния при обновлении страницы
5. Понимание концепций блока и модуля
Чанк — это пакет, состоящий из нескольких модулей.Чанк должен включать в себя несколько модулей.В общем случае он в конечном итоге образует файл. Для ресурсов, отличных от js, webpack преобразует его в модуль через различные загрузчики, этот модуль будет упакован в чанк и не будет образовывать отдельный чанк.
1. Компиляция веб-пакета
Webpack watch: Используйте режим мониторинга, чтобы начать компиляцию веб-пакета.Когда файл в файловой системе изменяется, веб-пакет отслеживает изменение файла, перекомпилирует и упаковывает модуль в соответствии с файлом конфигурации., каждая компиляция производитуникальное хэш-значение,
updated chunk (JavaScript)chunk名字.上一次编译生成的hash.hot-update.js(например, main.b1f49e2fc76aae861d9f.hot-update.js)Это вызывает глобальноеwebpackHotUpdateфункция, обратите внимание на структуру этого js
Да, эти два файла генерируются не вебпаком, а этим плагином.Вы можете удалить HotModuleReplacementPlugin в файле конфигурации и попробовать
Внедрить код времени выполнения HMR в файл фрагмента: Основная логика нашего клиента горячего обновления (拉取新模块代码,执行新模块代码,执行accept的回调实现局部更新) все, что этот плагин вводит функции в наши файлы чанков, а не webpack-dev-server, webpack-dev-server просто вызывает эти функции
2. Разберитесь с файлом пакета
Следующий код представляет собой фрагмент, сгенерированный компиляцией HotModuleReplacementPlugin, внедряет код среды выполнения HMR, запускает службу npm run dev, заходит на http://localhost:8000/main.js, перехватывает основную логику и сохраняет детали ( сначала присмотритесь, есть общее впечатление)
(function (modules) {
//(HMR runtime代码) module.hot属性就是hotCreateModule函数的执行结果,所有hot属性有accept、check等属性
function hotCreateModule() {
var hot = {
accept: function (dep, callback) {
for (var i = 0; i < dep.length; i++)
hot._acceptedDependencies[dep[i]] = callback;
},
check: hotCheck,//【在webpack/hot/dev-server.js中调用module.hot.accept就是hotCheck函数】
};
return hot;
}
//(HMR runtime代码) 以下几个方法是 拉取更新模块的代码
function hotCheck(apply) {}
function hotDownloadUpdateChunk(chunkId) {}
function hotDownloadManifest(requestTimeout) {}
//(HMR runtime代码) 以下几个方法是 执行新代码 并 执行accept回调
window["webpackHotUpdate"] = function webpackHotUpdateCallback(chunkId, moreModules) {
hotAddUpdateChunk(chunkId, moreModules);
};
function hotAddUpdateChunk(chunkId, moreModules) {hotUpdateDownloaded();}
function hotUpdateDownloaded() {hotApply()}
function hotApply(options) {}
//(HMR runtime代码) hotCreateRequire给模块parents、children赋值了
function hotCreateRequire(moduleId) {
var fn = function(request) {
return __webpack_require__(request);
};
return fn;
}
// 模块缓存对象
var installedModules = {};
// 实现了一个 require 方法
function __webpack_require__(moduleId) {
// 判断这个模块是否在 installedModules缓存 中
if (installedModules[moduleId]) {
// 在缓存中,直接返回 installedModules缓存 中该 模块的导出对象
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false, // 模块是否加载
exports: {}, // 模块的导出对象
hot: hotCreateModule(moduleId), // module.hot === hotCreateModule导出的对象
parents: [], // 这个模块 被 哪些模块引用了
children: [] // 这个模块 引用了 哪些模块
};
// (HMR runtime代码) 执行模块的代码,传入参数
modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
// 设置模块已加载
module.l = true;
// 返回模块的导出对象
return module.exports;
}
// 暴露 模块的缓存
__webpack_require__.c = installedModules;
// 加载入口模块 并且 返回导出对象
return hotCreateRequire(0)(__webpack_require__.s = 0);
})(
{
"./src/content.js":
(function (module, __webpack_exports__, __webpack_require__) {}),
"./src/index.js":
(function (module, exports, __webpack_require__) {}),// 在模块中使用的require都编译成了__webpack_require__
"./src/lib/client/emitter.js":
(function (module, exports, __webpack_require__) {}),
"./src/lib/client/hot/dev-server.js":
(function (module, exports, __webpack_require__) {}),
"./src/lib/client/index.js":
(function (module, exports, __webpack_require__) {}),
0:// 主入口
(function (module, exports, __webpack_require__) {
eval(`
__webpack_require__("./src/lib/client/index.js");
__webpack_require__("./src/lib/client/hot/dev-server.js");
module.exports = __webpack_require__("./src/index.js");
`);
})
}
);
Когда браузер выполняет этот фрагмент, при выполнении каждого модуляОбъект модуля будет передан для каждого модуля, структура следующая, иПоместите этот объект модуля в кешinstalledModulesв; мы можем пройти__webpack_require__.c拿到这个模块缓存对象
hotCreateRequire поможет нам присвоить значения родителям и детям модуля модуля
Затем посмотрите на свойство hot, что возвращает hotCreateModule(moduleId)? Вот такhot — это объект с двумя основными атрибутами: accept и check, то мы подробно разберем module.hot и module.hot.accept
function hotCreateModule() {
var hot = {
accept: function (dep, callback) {
for (var i = 0; i < dep.length; i++)
hot._acceptedDependencies[dep[i]] = callback;
},
check: hotCheck,
};
return hot;
}
3. Расскажите о module.hot и module.hot.accept
1. принять к использованию
Если вы хотите получить горячее обновление, следующий код необходим: Функция обратного вызова, передаваемая accept, представляет собой локальную логику обновления, которая выполняется при изменении модуля ./content.js.
if (module.hot) {
module.hot.accept(["./content.js"], render);
}
2. Принцип принятия
Почему мы пишем толькоmodule.hot.accept(["./content.js"], render);Чтобы добиться горячего обновления, мы должны начать с принципа функции принятия.Давайте посмотрим на module.hot и module.hot.accept.
function hotCreateModule() {
var hot = {
accept: function (dep, callback) {
for (var i = 0; i < dep.length; i++)
hot._acceptedDependencies[dep[i]] = callback;
},
};
return hot;
}
var module = installedModules[moduleId] = {
// ...
hot: hotCreateModule(moduleId),
};
Да, принятьhot._acceptedDependenciesХранилище объектов Локальная функция обратного вызова обновления, Когда будет использоваться _acceptedDependencies? (Когда файл модуля изменяется, мы вызываем обратный вызов, собранный с помощью acceptDependencies.)
1. Весь процесс разделен на клиентский и серверный
2. ПройтиwebsocketУстановить связь между браузером и сервером
3. Сервер в основном разделен на четыре ключевые точки
Создайте экземпляр компилятора через веб-пакет, и веб-пакет компилируется в режиме просмотра.
Экземпляр компилятора: отслеживать изменения локальных файлов, автоматически компилировать изменения файлов, компилировать выходные данные
Измените атрибут записи в конфигурации: добавьте lib/client/index.js, lib/client/hot/dev-server.js в файл фрагмента упакованного вывода.
Зарегистрируйте событие в хуке компилятора.hooks.done (срабатывает после компиляции веб-пакета): оно будет передано клиенту.hashа такжеokмероприятие
Вызываем webpack-dev-middleware: запускаем компиляцию, устанавливаем файл в файловую систему памяти, а в нем есть middleware, который отвечает за отдачу скомпилированного файла
Создайте службу веб-сокетов: установите двустороннюю связь между локальной службой и браузером; всякий раз, когда происходит новая компиляция, немедленно сообщайте браузеру о выполнении логики горячего обновления.
4. Клиент в основном делится на два ключевых момента
Создайте клиент веб-сокета для подключения к серверу веб-сокета, и клиент веб-сокета прослушиваетhashа такжеokмероприятие
Основная логика реализации клиента горячего обновления, браузер получит сообщение, отправленное сервером, если требуется горячее обновление, браузер инициирует http-запрос к серверу, чтобы получить анализ ресурсов нового модуля и локально обновить страницу (это что делает для нас HotModuleReplacementPlugin.Да, он внедрил в чанк исполняемый код HMR, но я покажу вам, как это реализоватьHMR runtime)
3. Измените атрибут записи веб-пакета, добавьте клиентский файл веб-сокета и дайте ему скомпилироваться в фрагмент.
Перед компиляцией веб-пакета вызовитеupdateCompiler(compiler)метод, этот метод очень важен, он скрытно вставит в наш чанк два файла,lib/client/client.jsа такжеlib/client/hot-dev-server.js
Для чего эти два файла? Мы сказали, что используя websocket для достижения двусторонней связи, наш сервер создаст сервер websocket (на шаге 9), и каждый раз, когда код будет изменен, он будет перекомпилирован для создания нового скомпилированного файла, после чего наш сервер websocket уведомит браузер., ты приходишь и дергаешь новый код
Так есть ли клиент websocket, который реализует логику общения с сервером? Таким образом, webpack-dev-server предоставляет нам код на стороне клиента, то есть два вышеуказанных файла, и устанавливает для нас программу-шпион, чтобы мы могли незаметно получать новый код и добиваться горячих обновлений.
Зачем разбивать на два файла? Конечно это разделение модулей.Нехорошо писать балабалу одним куском.В клиентской части реализации я подробнее остановлюсь на том, что делают эти два файла.
Мы должны зарегистрировать событие на крючке, выполненном в компиляторе, и это событие в основном делает одно. Всякий раз, когда новая компиляция завершена, отправьте сообщение всем клиентам Websocket, передавать два события, уведомить браузер, чтобы вытащить код LA
Браузер будет прослушивать эти два события, а браузер потянет его上次编译生成的hash.hot-update.json, конкретная логика будет подробно объяснена в разделе клиента ниже
6. Добавьте промежуточное ПО webpack-dev-middleware
1. О webpack-dev-server и webpack-dev-middleware
Ядром webpack-dev-server является выполнение подготовительной работы (изменение записи, отслеживание событий webpack done и т. д.), создание сервера веб-сервера и сервера веб-сокетов, чтобы позволить браузеру и серверу установить связь.
Операции, связанные с компиляцией и компиляцией файлов, извлекаются в webpack-dev-middleware
2. WebPack-Dev-Madeware в основном делает три вещи (здесь мы реализуем свою собственную логику)
Следите за локальными файлами и запускайте компиляцию веб-пакета, используйте режим мониторинга для запуска компиляции веб-пакета В режиме наблюдения веб-пакета, когда файл в файловой системе изменяется, веб-пакет отслеживает изменения файла, перекомпилирует и упаковывает модуль в соответствии с файлом конфигурации;
Установите файловую систему в файловую систему в памяти (пусть компиляция выводит в память)
Здесь используется экспресс и нативный http, у вас могут возникнуть вопросы? Почему бы просто не использовать экспресс или http?
Мы не используем экспресс напрямую, потому что мы не можем получить сервер Мы можем посмотреть исходный код экспресса Зачем нам нужен этот сервер, потому что мы хотим использовать его в сокете;
Если вы не используете http напрямую, вы должны знать, что нативная логика написания http не повредит; мы просто написали здесь простую статическую логику обработки, так что мы ничего не видим, но в исходный код, вот только ядро Logic выбирает и реализует
Так как у обоих есть недочеты, давайте их объединим.Сделаем сервис с нативным http и получим сервер.Логику запросов этого сервера должен обрабатывать экспресс.this.server = http.createServer(app);Одна строка кода идеальна
2. Расскажите о webpack/hot/dev-server.js в исходном коде.
Мы говорили, что webpack-dev-server.js будет вupdateCompiler(compiler)Измените конфигурацию входа наwebpack-dev-server/client/index.js?http://localhost:8080а такжеwebpack/hot/dev-server.jsСоберите их в куски вместе, затем раскройте истинное лицо Hot / Deverserver.js в исходном коде, да, следующее является основным кодом
// 源码中webpack/hot/dev-server.js
if (module.hot) {// 是否支持热更新
var check = function check() {
module.hot
.check(true)// 没错module.hot.check就是hotCheck函数,看是不是绕到了HRMPlugin在打包的chunk中注入的HMR runtime代码啦
.then( /*日志输出*/)
.catch( /*日志输出*/)
};
// 和client/index.js共用一个EventEmitter实例,这里用于监听事件
var hotEmitter = require("./emitter");
// 监听webpackHotUpdate事件
hotEmitter.on("webpackHotUpdate", function(currentHash) {
check();
});
} else {
throw new Error("[HMR] Hot Module Replacement is disabled.");
}
Понятно, реальная логика горячего обновления на стороне клиента выполняется кодом среды выполнения HotModuleReplacementPlugin.runtime через module.hot.check=hotCheck.webpack/hot/dev-server.jsа такжеHotModuleReplacementPlugin在chunk文件中注入的hotCheck等代码построить мост
3. Общий обзор hot/dev-server.js
Отличие с исходным кодом: hot/dev-server.js в исходном коде очень простое, то есть он вызывает module.hot.check (то есть hotCheck, когда запущена среда выполнения HMR). Код, вставленный HotModuleReplacementPlugin, является ядром клиента горячего обновления.
Теперь давайте взглянем на общий hot/dev-server.js, который мы хотим реализовать.
let hotEmitter = require("../emitter");// 和client.js公用一个EventEmitter实例
let currentHash;// 最新编译生成的hash
let lastHash;// 表示上一次编译生成的hash,源码中是hotCurrentHash,为了直接表达他的字面意思换了个名字
//【4】监听webpackHotUpdate事件,然后执行hotCheck()方法进行检查
hotEmitter.on("webpackHotUpdate", (hash) => {
hotCheck();
})
//【5】调用hotCheck拉取两个补丁文件
let hotCheck = () => {
hotDownloadManifest().then(hotUpdate => {
hotDownloadUpdateChunk(chunkID);
})
}
// 【6】拉取lashhash.hot-update.json,向 server 端发送 Ajax 请求,服务端返回一个 Manifest文件(lasthash.hot-update.json),该 Manifest 包含了本次编译hash值 和 更新模块的chunk名
let hotDownloadManifest = () => {}
// 【7】拉取更新的模块chunkName.lashhash.hot-update.json,通过JSONP请求获取到更新的模块代码
let hotDownloadUpdateChunk = (chunkID) => {}
// 【8.0】这个hotCreateModule很重要,module.hot的值 就是这个函数执行的结果
let hotCreateModule = (moduleID) => {
let hot = {
accept() {},
check: hotCheck
}
return hot;
}
//【8】补丁JS取回来后会调用webpackHotUpdate方法(请看update chunk的格式),里面会实现模块的热更新
window.webpackHotUpdate = (chunkID, moreModules) => {
//【9】热更新的重点代码实现
}
4. Прослушайте событие webpackHotUpdate
Отличие с исходным кодом: в исходном коде вызывается метод проверки, а в методе проверки вызывается метод module.hot.check, то есть метод hotCheck, и в проверке также выполняется некоторый вывод лога. Здесь прямо пишем основной метод hotCheck в чеке
называетсяwebpackHotUpdateметод, показывающий, что мы должны全局上有一个webpackHotUpdateметод
Отличие от исходного кода: В исходном коде будет вызываться метод hotAddUpdateChunk webpackHotUpdate для динамического обновления кода модуля (замена старого модуля на новый модуль), а затем для горячего обновления будет вызываться метод hotApply. ядро этих методов написано прямо в webpackHotUpdate
Позвольте веб-пакету скомпилироваться в режиме просмотра;
Измените файловую систему на файловую систему в памяти, которая не будет записывать упакованные ресурсы на диск, а будет обрабатывать их в памяти;
Промежуточное ПО отвечает за возврат скомпилированного файла;
2. Webpack-горячее промежуточное ПО:
Обеспечивает механизм связи между браузером и сервером Webpack, подписывается и получает обновления от сервера Webpack на стороне браузера, а затем использует HMR API webpack для выполнения этих изменений.
1. Сервер
События компилятора.hooks.done монитора сервера;
Через SSE сервер компилирует и отправляет клиенту события сборки, построения и синхронизации;
webpack-dev-middleware черезEventSourceтакже называемыйserver-sent-event(SSE)Для достижения односторонних push-сообщений от сервера к клиенту. Благодаря обнаружению сердцебиения, чтобы определить, жив ли клиент, это 💓 SSE.Обнаружение сердцебиения, установитеsetIntervalКаждые 10 с отправляет время клиенту
2. Клиент
Тот же код клиента необходимо добавить в атрибут записи config,
создание клиентаEventSourceзапрос экземпляра/__webpack_hmr, слушать события сборки, сборки, синхронизации, функция обратного вызова будет обновлена кодом времени выполнения HotModuleReplacementPlugin;
3. Резюме
Фактически, когда мы внедрили горячее обновление webpack-dev-server, мы уже реализовали функции webpack-hot-middleware.
Их самая большая разница заключается в средствах связи между браузером и сервером.webpack-dev-serverиспользуетwebsocket,webpack-hot-middlewareиспользуетeventSource; и имена событий коммуникационного процесса отличаются, webpack-dev-server использует хэш и ok, webpack-dev-middleware находится в сборке (в разработке, горячее обновление не будет запускаться) и синхронизации (чтобы определить, следует ли запускать горячее процесс обновления)
3. webpack-dev-server
Webpack-Dev-Server — это встроенное ПО Webpack-dev-middleware и сервер Express, использующийwebsocketзаменятьeventSourceРеализовать логику webpack-hot-middleware
4. Разница
В: Почему уwebpack-dev-server, и здесьwebpack-dev-middlewareсоответствоватьwebpack-hot-middlewareспособ?
A: webpack-dev-serverупаковано, кромеwebpack.configи аргументы командной строки, сложно настроить разработку. При строительстве строительных лесов используйтеwebpack-dev-middlewareа такжеwebpack-hot-middlewareи серверные службы, чтобы сделать разработку более гибкой.
После завершения компиляции сообщение отправляется клиенту веб-сокета.Самая важная информация — новый модуль.hashзначение, следующие шаги основаны на этомhashстоимость горячей замены модуля
Запустить компиляцию веб-пакета в режиме наблюдения, файл в файловой системе изменен, веб-пакет отслеживает изменение файла, перекомпилирует и упаковывает модуль в соответствии с файлом конфигурации.
Вызовите reloadApp, в reloadApp рассудят, поддерживается ли горячее обновление, если поддерживается, запускаемwebpackHotUpdateсобытие, если оно не поддерживается, обновить браузер напрямую
Вызов HotDownloadManifest` отправляет запрос AJAX на сторону сервера, сервер возвращает файл манифеста (Lasthash.hot-update.json), который содержит имя чанка этого скомпилированного значения хэша и модуль обновления.