Замена модулей в режиме реального времени
Замена модулей в режиме реального времени (или HMR) — одна из самых полезных функций, предлагаемых webpack. Она позволяет обновлять любые модули во время выполнения без необходимости полной перезагрузки. Эта страница фокусируется на реализации, а страница концепций предоставляет более подробную информацию о том, как это работает и почему это полезно.
Включение HMR
Эта функция отлично подходит для повышения производительности. Всё, что нам нужно сделать, это обновить конфигурацию нашего webpack-dev-server и использовать встроенный плагин HMR webpack. Мы также удалим точку входа для print.js , так как теперь она будет использоваться модулем index.js.
Начиная с webpack-dev-server версии 4.0.0, замена модулей в режиме реального времени включена по умолчанию.
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
- print: './src/print.js',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
+ hot: true,
},
plugins: [
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
}; Вы также можете предоставить точки входа для HMR вручную:
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const webpack = require("webpack");
module.exports = {
entry: {
app: './src/index.js',
- print: './src/print.js',
+ // Runtime code for hot module replacement
+ hot: 'webpack/hot/dev-server.js',
+ // Dev server client for web socket transport, hot and live reload logic
+ client: 'webpack-dev-server/client/index.js?hot=true&live-reload=true',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
+ // Dev server client for web socket transport, hot and live reload logic
+ hot: false,
+ client: false,
},
plugins: [
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
+ // Plugin for hot module replacement
+ new webpack.HotModuleReplacementPlugin(),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
}; Теперь обновим файл index.js, чтобы при обнаружении изменений в print.js, webpack принимал обновленный модуль.
index.js
import _ from 'lodash';
import printMe from './print.js';
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe;
element.appendChild(btn);
return element;
}
document.body.appendChild(component());
+
+ if (module.hot) {
+ module.hot.accept('./print.js', function() {
+ console.log('Accepting the updated printMe module!');
+ printMe();
+ })
+ } Начните изменять оператор console.log в print.js, и вы увидите следующий вывод в консоли браузера (не беспокойтесь пока о выводе button.onclick = printMe, мы также обновим эту часть позже).
print.js
export default function printMe() {
- console.log('I get called from print.js!');
+ console.log('Updating print.js...');
} консоль
[HMR] Waiting for update signal from WDS... main.js:4395 [WDS] Hot Module Replacement enabled. + 2main.js:4395 [WDS] App updated. Recompiling... + main.js:4395 [WDS] App hot update... + main.js:4330 [HMR] Checking for updates on the server... + main.js:10024 Accepting the updated printMe module! + 0.4b8ee77….hot-update.js:10 Updating print.js... + main.js:4330 [HMR] Updated modules: + main.js:4330 [HMR] - 20
Через API Node.js
При использовании Webpack Dev Server с API Node.js не размещайте параметры сервера разработки в объекте конфигурации webpack. Вместо этого передайте их в качестве второго параметра при создании. Например:
new WebpackDevServer(options, compiler)
Для включения HMR вам также нужно изменить объект конфигурации webpack, добавив в него точки входа HMR. Вот небольшой пример того, как это может выглядеть:
dev-server.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const webpackDevServer = require('webpack-dev-server');
const config = {
mode: 'development',
entry: [
// Runtime code for hot module replacement
'webpack/hot/dev-server.js',
// Dev server client for web socket transport, hot and live reload logic
'webpack-dev-server/client/index.js?hot=true&live-reload=true',
// Your entry
'./src/index.js',
],
devtool: 'inline-source-map',
plugins: [
// Plugin for hot module replacement
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
const compiler = webpack(config);
// `hot` and `client` options are disabled because we added them manually
const server = new webpackDevServer({ hot: false, client: false }, compiler);
(async () => {
await server.start();
console.log('dev server is running');
})(); См. полную документацию API webpack-dev-server Node.js.
Особенности
Замена модулей в режиме реального времени может быть сложной. Давайте вернёмся к нашему рабочему примеру. Если вы нажмёте кнопку на странице примера, вы увидите, что в консоли выводится старая функция printMe.
Это происходит потому, что обработчик события кнопки onclick по-прежнему связан со старой функцией printMe.
Чтобы это работало с HMR, нам нужно обновить эту привязку к новой функции printMe с помощью module.hot.accept:
index.js
import _ from 'lodash';
import printMe from './print.js';
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe; // onclick event is bind to the original printMe function
element.appendChild(btn);
return element;
}
- document.body.appendChild(component());
+ let element = component(); // Store the element to re-render on print.js changes
+ document.body.appendChild(element);
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
- printMe();
+ document.body.removeChild(element);
+ element = component(); // Re-render the "component" to update the click handler
+ document.body.appendChild(element);
})
} Это лишь один пример, но есть и другие, которые легко могут запутать. К счастью, существует множество лоадеров (некоторые из них перечислены ниже), которые сделают замену модулей в режиме реального времени намного проще.
HMR со стилями
Замена модулей в режиме реального времени со стилями на самом деле довольно проста с помощью style-loader. Этот лоадер использует module.hot.accept в фоновом режиме для исправления тегов <style> при обновлении зависимостей CSS.
Сначала установим оба лоадера с помощью следующей команды:
npm install --save-dev style-loader css-loader
Теперь обновим файл конфигурации, чтобы использовать этот лоадер.
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
hot: true,
},
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader'],
+ },
+ ],
+ },
plugins: [
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
}; Загрузку стилей в режиме реального времени можно выполнить, импортировав их в модуль:
проект
webpack-demo
| - package.json
| - webpack.config.js
| - /dist
| - bundle.js
| - /src
| - index.js
| - print.js
+ | - styles.css styles.css
body {
background: blue;
} index.js
import _ from 'lodash';
import printMe from './print.js';
+ import './styles.css';
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe; // onclick event is bind to the original printMe function
element.appendChild(btn);
return element;
}
let element = component();
document.body.appendChild(element);
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
document.body.removeChild(element);
element = component(); // Re-render the "component" to update the click handler
document.body.appendChild(element);
})
}
Измените стиль на body на background: red;, и вы сразу увидите, как цвет фона страницы изменится без полной перезагрузки.
styles.css
body {
- background: blue;
+ background: red;
} Другой код и фреймворки
В сообществе существует множество других лоадеров и примеров, позволяющих HMR взаимодействовать с различными фреймворками и библиотеками…
- React Hot Loader: Настройка компонентов react в режиме реального времени.
- Vue Loader: Этот лоадер поддерживает HMR для компонентов vue из коробки.
- Elm Hot webpack Loader: Поддерживает HMR для языка программирования Elm.
-
HMR Angular: Никаких лоадеров не требуется! Поддержка HMR встроена в Angular CLI, добавьте флаг
--hmrк вашей командеng serve. - Svelte Loader: Этот лоадер поддерживает HMR для компонентов Svelte из коробки.
© JS Foundation and other contributors
Licensed under the Creative Commons Attribution License 4.0.
https://webpack.js.org/guides/hot-module-replacement