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) Не настроено.
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.
Вы также можете прочитать статьи, на которые есть ссылки, но, поскольку версии исходного кода различаются, не слишком запутывайтесь в деталях.
Оригинально не просто, пройди через точку похвалы ~ 😊