1. Предисловие - горячее обновление webpack
Hot Module Replacement
, именуемыйHMR
, обновите модуль без полного обновления всей страницы одновременно.HMR
Преимущества этого глубоко испытаны в ежедневной работе по развитию:Сэкономьте драгоценное время разработки и улучшите опыт разработки.
Обновление обычно делится на два типа:
- Один из них — обновить страницу без сохранения состояния страницы, что является простым и грубым, напрямую
window.location.reload()
. - Другой основан на
WDS (Webpack-dev-server)
Ему нужно только частично обновить модули, которые изменились на странице, и в то же время он может сохранить текущий статус страницы, такой как выбранный статус флажка, ввод поля ввода и т. д.
HMR
какWebpack
встроенные функции, которые можноHotModuleReplacementPlugin
или--hot
на. Так,HMR
Как добиться горячего обновления в итоге? Давайте узнаем!
Во-вторых, процесс компиляции и создания веб-пакета.
После того, как проект запущен, собран и упакован, консоль выведет процесс сборки, и мы можем наблюдать, чтоХэш-значение:a93fd735d02d98633356
.
Compiling…
Слова, запускающие новую компиляцию... можно наблюдать в консоли:
-
новое хэш-значение:
a61bdd6e82294ed06fa3
-
новый JSON-файл:
a93fd735d02d98633356.hot-update.json
-
новый js-файл:
index.a93fd735d02d98633356.hot-update.js
Во-первых, мы знаемHash
Значение представляет флаг для каждой компиляции. Во-вторых, по вновь сгенерированному имени файла можно обнаружить, что последний выводHash
Это значение будет использовано в качестве вновь созданного идентификатора файла для этой компиляции. И так далее, вывод этого времениHash
Это значение будет использоваться в качестве флага для следующего горячего обновления.
Тогда взгляните, что представляет собой вновь сгенерированный файл? При каждом изменении кода с последующей перекомпиляцией браузер делает 2 запроса. На этот раз запрос представляет собой два вновь сгенерированных файла. следующим образом:
Первый взглядjson
файл, в возвращаемом результате,h
представляет вновь созданныйHash
Значение, префикс, используемый для следующего запроса на горячее обновление файла.c
Указывает, что текущий файл для горячего обновления соответствуетindex
модуль.
Посмотрите на сгенерированныйjs
файл, то есть модифицированный код, перекомпилированный и упакованный.
-
новое хэш-значение:
d2e4208eca62aa1c5389
-
новый JSON-файл:
a61bdd6e82294ed06fa3.hot-update.json
Но мы обнаружили, не генерирует новыеjs
файл, потому что код не был изменен, и запрос, отправленный браузером, можно увидетьc
Если значение пустое, это означает, что на этот раз нет кода, который нужно обновить.
Шепот вниз,webapck
Так было в предыдущих версияхhash
Значение не изменится, и оно может быть пересмотрено позже по какой-то причине. Не обращайте внимания на детали, поймите принцип - это истинный смысл!!!
Подумайте наконец 🤔, как браузер узнает, что нативный код перекомпилирован, и быстро запросит вновь сгенерированный файл? Кто проинформировал браузер? Как браузер успешно обновляет эти файлы? Тогда давайте посмотрим на процесс горячего обновления с сомнениями и посмотрим на принцип с точки зрения исходного кода.
3. Принцип реализации горячего обновления
Я считаю, что все будут настроитьwebpack-dev-server
Горячее обновление, примеры показывать не буду. Вы можете проверить это онлайн. Далее мы рассмотримwebpack-dev-server
Как добиться горячего обновления? (Исходный код упрощен, в первой строке будет указан путь к коду, лучше всего есть его с исходником после прочтения).
1. webpack-dev-server запускает локальную службу
Мы основаны наwebpack-dev-server
изpackage.json
серединаbin
команда, вы можете найти входной файл командыbin/webpack-dev-server.js
.
// node_modules/webpack-dev-server/bin/webpack-dev-server.js
// 生成webpack编译主引擎 compiler
let compiler = webpack(config);
// 启动本地服务
let server = new Server(compiler, options, log);
server.listen(options.port, options.host, (err) => {
if (err) {throw err};
});
Местный сервисный код:
// node_modules/webpack-dev-server/lib/Server.js
class Server {
constructor() {
this.setupApp();
this.createServer();
}
setupApp() {
// 依赖了express
this.app = new express();
}
createServer() {
this.listeningApp = http.createServer(this.app);
}
listen(port, hostname, fn) {
return this.listeningApp.listen(port, hostname, (err) => {
// 启动express服务后,启动websocket服务
this.createSocketServer();
}
}
}
Этот раздел кода в основном делает три вещи:
- запускать
webpack
, сгенерироватьcompiler
пример.compiler
Есть много методов, таких как вы можете начатьwebpack
всекомпилироватьработа имониторИзменения в локальных файлах. - использовать
express
Фреймворк запускается локальноserver
Пусть браузер может запросить локальныйстатические ресурсы. - местный
server
После запуска перейти к началуwebsocket
обслуживание, если не понялwebsocket
, рекомендуется кратко понятьвеб-сокет экспресс. пройти черезwebsocket
, вы можете установить двустороннюю связь между локальной службой и браузером. Таким образом, когда локальный файл изменяется, он немедленно сообщает браузеру, что код может быть обновлен в горячем режиме!
Вышеприведенный код в основном делает три вещи, но исходный код делает много вещей перед запуском службы, так что давайте посмотримwebpack-dev-server/lib/Server.js
Что еще ты сделал?
2. Измените конфигурацию записи webpack.config.js.
Прежде чем запускать местную службу, позвонитеupdateCompiler(this.compiler)
метод. В этом методе есть 2 критических фрагмента кода. Один должен получитьwebsocket
Путь кода клиента, другой получается в соответствии с конфигурациейwebpack
Пути кода горячего обновления.
// 获取websocket客户端代码
const clientEntry = `${require.resolve(
'../../client/'
)}?${domain}${sockHost}${sockPath}${sockPort}`;
// 根据配置获取热更新代码
let hotEntry;
if (options.hotOnly) {
hotEntry = require.resolve('webpack/hot/only-dev-server');
} else if (options.hot) {
hotEntry = require.resolve('webpack/hot/dev-server');
}
после редактированияwebpack
Конфигурация входа следующая:
// 修改后的entry入口
{ entry:
{ index:
[
// 上面获取的clientEntry
'xxx/node_modules/webpack-dev-server/client/index.js?http://localhost:8080',
// 上面获取的hotEntry
'xxx/node_modules/webpack/hot/dev-server.js',
// 开发配置的入口
'./src/index.js'
],
},
}
Почему добавляются 2 новых файла? На вход молча добавляются два файла, а это значит, что они будут упакованы вместе вbundle
файл, то есть онлайн-среда выполнения.
(1) Webpack-dev-сервер/клиент/index.js
Сначала этот файл используется дляwebsocket
, потому чтоwebsoket
это двусторонняя связь, если вы не понимаетеwebsocket
, рекомендуется кратко понятьвеб-сокет экспресс. мы на шаге 1webpack-dev-server
Во время инициализации начнется локальный серверwebsocket
. Тогда клиент — это наш браузер, а у браузера нет кода для связи с сервером? Вы не можете позволить разработчикам писать это ххххх. Поэтому нам нужноwebsocket
Код связи на стороне клиента скрыт в нашем коде. Конкретный код клиента будет подробно рассмотрен позже в нужное время.
(2) webpack/hot/dev-server.js
Этот файл в основном используется для проверки логики обновления.Все его здесь знают.Код вернется в нужное время(Шаг 5) в деталях.
3. Следите за окончанием компиляции веб-пакета
После изменения конфигурации входа снова вызовитеsetupHooks
метод. Этот метод используется для регистрации и мониторинга событий и мониторинга каждый раз, когдаwebpack
Компиляция завершена.
// node_modules/webpack-dev-server/lib/Server.js
// 绑定监听事件
setupHooks() {
const {done} = compiler.hooks;
// 监听webpack的done钩子,tapable提供的监听方法
done.tap('webpack-dev-server', (stats) => {
this._sendStats(this.sockets, this.getStats(stats));
this._stats = stats;
});
};
при прослушиванииwebpack
После компиляции вызовет_sendStats
метод переданwebsocket
отправить уведомление в браузер,ok
а такжеhash
событие, чтобы браузер мог получать последниеhash
значение, выполните проверку логики обновления.
// 通过websoket给客户端发消息
_sendStats() {
this.sockWrite(sockets, 'hash', stats.hash);
this.sockWrite(sockets, 'ok');
}
4. webpack прослушивает изменения файлов
Компиляция запускается каждый раз при изменении кода. Это означает, что нам также необходимо отслеживать изменения в локальном коде, в основном черезsetupDevMiddleware
метод реализован.
Этот метод в основном выполняетwebpack-dev-middleware
библиотека. Многие люди не могут отличитьwebpack-dev-middleware
а такжеwebpack-dev-server
разница. На самом деле, потому чтоwebpack-dev-server
Отвечает только за запуск сервиса и подготовительные работы, все операции с файлами извлекаются наwebpack-dev-middleware
Библиотеки, в основном локальные файлыкомпилироватьа такжевыходтак же какмонитор, есть не что иное, как более четкое разделение обязанностей.
Тогда давайте посмотримwebpack-dev-middleware
Что сделано в исходном коде:
// node_modules/webpack-dev-middleware/index.js
compiler.watch(options.watchOptions, (err) => {
if (err) { /*错误处理*/ }
});
// 通过“memory-fs”库将打包后的文件写入内存
setFs(context, compiler);
(1) называетсяcompiler.watch
Метод, также упомянутый на шаге 1,compiler
власти. Этот метод в основном делает 2 вещи:
- Сначала скомпилируйте и упакуйте код локального файла, то есть
webpack
Серия процессов компиляции. - Во-вторых, после завершения компиляции включается мониторинг локального файла, при изменении файла он перекомпилируется, и мониторинг продолжается после завершения компиляции.
Почему сохраненное изменение кода автоматически компилируется и переупаковывается? Эта серия перепроверочных компиляций приписываетсяcompiler.watch
Этот метод. Мониторинг изменений в локальных файлах в основном осуществляется черезвремя создания файлаБудут ли изменения, я не буду здесь вдаваться в подробности.
(2) ВыполнитьsetFs
метод, основная цель этого метода — упаковать скомпилированный файл в память. Вот почему в процессе разработки вы обнаружитеdist
В каталоге нет упакованного кода, потому что он весь находится в памяти. Причина в том, что доступ к коду в памяти выполняется быстрее, чем к файлам в файловой системе, а также снижает накладные расходы на запись кода в файл, все благодаряmemory-fs
.
5. Браузер получает уведомление о горячем обновлении
Мы уже можем отслеживать изменения файлов и запускать перекомпиляцию при изменении файла. В то же время он также прослушивает событие завершения каждой компиляции. при прослушиванииwebpack
Сборка заканчивается,_sendStats
метод черезwebsoket
Отправьте уведомление в браузер, чтобы проверить, требуется ли горячее обновление. Далее делается акцент на_sendStats
в методеok
а такжеhash
Что дало событие.
Как браузер получаетwebsocket
Новости? Вспомните файл записи, добавленный на шаге 2, которыйwebsocket
код клиента.
'xxx/node_modules/webpack-dev-server/client/index.js?http://localhost:8080'
Код для этого файла будет упакован вbundle.js
, работающий в браузере. Давайте посмотрим на основной код этого файла.
// webpack-dev-server/client/index.js
var socket = require('./socket');
var onSocketMessage = {
hash: function hash(_hash) {
// 更新currentHash值
status.currentHash = _hash;
},
ok: function ok() {
sendMessage('Ok');
// 进行更新检查等操作
reloadApp(options, status);
},
};
// 连接服务地址socketUrl,?http://localhost:8080,本地服务地址
socket(socketUrl, onSocketMessage);
function reloadApp() {
if (hot) {
log.info('[WDS] App hot update...');
// hotEmitter其实就是EventEmitter的实例
var hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
}
}
socket
метод установленwebsocket
Подключитесь к серверу и зарегистрируйте 2 события прослушивателя.
-
hash
событие, обновите последний пакетhash
стоимость. -
ok
событие, выполните горячую проверку обновлений.
Событие проверки горячего обновления называетсяreloadApp
метод. Как ни странно, этот метод используетnode.js
изEventEmitter
,проблемаwebpackHotUpdate
Информация. почему это? Почему бы просто не проверить наличие обновлений?
Личное понимание необходимо для лучшего сопровождения кода и более четкого разделения обязанностей.websocket
Используется только для связи клиента (браузера) и сервера. И настоящая работа по ведению дел возвращается кwebpack
.
Этоwebpack
Как это сделать? Вспомним шаг 2. В файле ввода есть еще один файл, который не упоминается, а именно:
'xxx/node_modules/webpack/hot/dev-server.js'
Код этого файла также будет упакован вbundle.js
, работающий в браузере. Очевидно, что делает этот файл! Сначала взгляните на код:
// node_modules/webpack/hot/dev-server.js
var check = function check() {
module.hot.check(true)
.then(function(updatedModules) {
// 容错,直接刷新页面
if (!updatedModules) {
window.location.reload();
return;
}
// 热更新结束,打印信息
if (upToDate()) {
log("info", "[HMR] App is up to date.");
}
})
.catch(function(err) {
window.location.reload();
});
};
var hotEmitter = require("./emitter");
hotEmitter.on("webpackHotUpdate", function(currentHash) {
lastHash = currentHash;
check();
});
здесьwebpack
подслушалwebpackHotUpdate
события и получайте последниеhash
значение и, наконец, проверьте наличие обновлений. Как насчет проверки обновлений?module.hot.check
метод. Потом снова возникает проблемаmodule.hot.check
Откуда это! ответHotModuleReplacementPlugin
Черт возьми. Оставьте вопрос здесь, продолжайте читать.
6. HotModuleReplacementPlugin
Кажется, так было всегдаwebpack-dev-server
сделай этоHotModuleReplacementPlugin
Что хорошего вы сделали во время горячего обновления?
В первую очередь можно сравнить, при настройке горячего обновления и при не настройкеbundle.js
разница. В памяти не видно? прямое исполнениеwebpack
команда для просмотра сгенерированногоbundle.js
файл. Не используйте егоwebpack-dev-server
Просто запустите.
(1) Не настроено.
(2) НастроеноHotModuleReplacementPlugin
или--hot
из.О~ мы нашлиmoudle
Добавлено свойство какhot
, посмотри сноваhotCreateModule
метод.
это не найденоmodule.hot.check
Откуда это.
После сравнения упакованных файлов__webpack_require__
серединаmoudle
И разница в количестве строк кода. мы все можем найтиHotModuleReplacementPlugin
Оказалось, что он незаметно вставляет много кода вbundle.js
в. Это очень похоже на шаг 2! Почему, ведь проверка обновлений делается в браузере. Код должен находиться в среде выполнения.
Вы также можете посмотреть прямо в браузереSources
Код ниже найдетwebpack
а такжеplugin
Тайно добавленный код все там. Отладка здесь также очень удобна.
HotModuleReplacementPlugin
Как? Я не буду говорить об этом здесь, потому что это требует от васtapable
так же какplugin
У меня есть определенное понимание механизма, вы можете прочитать статью, которую я написалАнализ Tapable-исходного кода механизма плагинов Webpack. Конечно, вы также можете пропустить его и заботиться только о механизме горячего обновления, ведь объем информации слишком велик.
7. module.hot.check запустить горячее обновление
С шагом 6 мы знаемmoudle.hot.check
Как появился метод. Что это сделало? Исходный код после этогоHotModuleReplacementPlugin
набитый вbundle.js
Ой, я не буду писать путь к файлу.
- Используйте последнюю сохраненную
hash
стоимость, звонитеhotDownloadManifest
Отправитьxxx/hash.hot-update.json
изajax
просить; - Результат запроса получает модуль горячего обновления, а следующее горячее обновление
Hash
и войдите в стадию подготовки горячего обновления.
hotAvailableFilesMap = update.c; // 需要更新的文件
hotUpdateNewHash = update.h; // 更新下次热更新hash值
hotSetStatus("prepare"); // 进入热更新准备状态
- передача
hotDownloadUpdateChunk
Отправитьxxx/hash.hot-update.js
запрос, черезJSONP
Способ.
function hotDownloadUpdateChunk(chunkId) {
var script = document.createElement("script");
script.charset = "utf-8";
script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";
if (null) script.crossOrigin = null;
document.head.appendChild(script);
}
Почему это тело функции должно быть вынесено отдельно, ведь оно для этого и используетсяJSONP
Получить последний код? В основном потому, чтоJSONP
Полученный код может быть непосредственно выполнен. Почему прямое исполнение? давай вспомним/hash.hot-update.js
Какой формат кода.
Можно обнаружить, что только что скомпилированный код находится вwebpackHotUpdate
внутри тела функции. который должен быть выполнен немедленноwebpackHotUpdate
Сюда.
посмотри сноваwebpackHotUpdate
Сюда.
window["webpackHotUpdate"] = function (chunkId, moreModules) {
hotAddUpdateChunk(chunkId, moreModules);
} ;
-
hotAddUpdateChunk
метод поместит обновленный модульmoreModules
Назначить глобальному полномуhotUpdate
. -
hotUpdateDownloaded
метод вызоветhotApply
Сделайте замену кода.
function hotAddUpdateChunk(chunkId, moreModules) {
// 更新的模块moreModules赋值给全局全量hotUpdate
for (var moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
hotUpdate[moduleId] = moreModules[moduleId];
}
}
// 调用hotApply进行模块的替换
hotUpdateDownloaded();
}
8. Замена модуля горячего обновления hotApply
Основная логика горячего обновления находится вhotApply
метод.hotApply
Там почти 400 строк кода, так что остановлюсь на ключевых моментах, буду плакать😭
① Удаление модулей с истекшим сроком действия — это модуль, который необходимо заменить.
пройти черезhotUpdate
Можно найти старые модули
var queue = outdatedModules.slice();
while (queue.length > 0) {
moduleId = queue.pop();
// 从缓存中删除过期的模块
module = installedModules[moduleId];
// 删除过期的依赖
delete outdatedDependencies[moduleId];
// 存储了被删掉的模块id,便于更新代码
outdatedSelfAcceptedModules.push({
module: moduleId
});
}
②Добавить новый модуль в модули
appliedUpdate[moduleId] = hotUpdate[moduleId];
for (moduleId in appliedUpdate) {
if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
modules[moduleId] = appliedUpdate[moduleId];
}
}
③ Выполните код связанных модулей через __webpack_require__
for (i = 0; i < outdatedSelfAcceptedModules.length; i++) {
var item = outdatedSelfAcceptedModules[i];
moduleId = item.module;
try {
// 执行最新的代码
__webpack_require__(moduleId);
} catch (err) {
// ...容错处理
}
}
hotApply
Это действительно сложно.Полезно знать общий процесс.В этом разделе вы должны иметь некоторое представление о том, как выполняются файлы, упакованные webpack.Вы можете проверить это самостоятельно.
4. Резюме
Это все еще картинка, нарисованная в виде чтения исходного кода.Маленькие отметки ①-④ — это процесс изменения файлов.
напиши в конце
На этот раз я объясняю принцип, читая исходный код, потому что чувствую, что горячее обновление требует много знаний. Поэтому знания выносят ключевой код, потому что каждый блок подробностей можно написать в статье, а разобраться в исходнике каждый сможет сам.
Тем не менее, рекомендуется заранее усвоить следующие знания, чтобы лучше понимать горячие обновления:
- websocket:Базовые знания вебсокета
- упакованный
bundle
Как файл запустить. -
webpack
Начать процесс,webpack
Жизненный цикл. - tapable: Анализ Tapable-исходного кода механизма плагинов Webpack
Ссылка на ссылку
- Принципиальный анализ замены горячего модуля Webpack
- Прочитав это, интервьюеры больше не боятся вопросов о горячих обновлениях Webpack.
Вы также можете прочитать статьи, на которые есть ссылки, но, поскольку версии исходного кода различаются, не слишком запутывайтесь в деталях.
Оригинально не просто, пройди через точку похвалы ~ 😊