熱模塊替換(HMR)是webpack提供的最有用的功能之一,它讓各類模塊能夠在運行時更新而無需刷新,本篇主要注重於實現。css
ps:HMR是爲開發模式設計的,也只能用於開發模式。html
啓用HRM只須要更新webpack-dev-server的配置,而後使用webpack的內置插件,同時要刪掉print.js入口。若是你使用的是webpack-dev-middleware,請使用webpack-hot-middleware包去啓用HRM。vue
webpack.config.jsreact
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); + const webpack = require('webpack'); module.exports = { entry: { - app: './src/index.js', - print: './src/print.js' + app: './src/index.js' }, devtool: 'inline-source-map', devServer: { contentBase: './dist', + hot: true }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Hot Module Replacement' }), + new webpack.NamedModulesPlugin(), + new webpack.HotModuleReplacementPlugin() ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } };
你也可使用腳手架的webpack-dev-server --hotOnly來更新webpack-dev-server的這個配置。這裏還添加了NamedModulesPlugin以便更容易查看要修補(patch)的依賴。在起步階段,咱們將經過在命令行中運行 npm start
來啓動並運行 dev server。webpack
更新一下index.js使得當print.js文件有任何變化就通知webpack去更新模塊。web
index.jsnpm
import _ from 'lodash'; import printMe from './print.js'; function component() { var element = document.createElement('div'); var 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(); + }) + }
若是如今還沒把項目跑起來的話就npm start一下,而後修改print.js中的內容,你就會在瀏覽器的控制檯看到以下輸出:json
print.js瀏覽器
export default function printMe() { - console.log('I get called from print.js!'); + console.log('Updating print.js...') }
consoleapp
[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 + main.js:4330 [HMR] Consider using the NamedModulesPlugin for module names.
當使用dev server和Node.js API時,不要把dev server的選項放在webpack的配置對象中,而應該在建立選項時把它做爲第二個參數傳遞:
new WebpackDevServer(compiler, options)
啓用HRM還須要修改webpack的配置對象以包含HRM入口,webpack-dev-server包提供一個addDevSeverEntryPoints方法來作這件事:
dev-server.js
const webpackDevServer = require('webpack-dev-server'); const webpack = require('webpack'); const config = require('./webpack.config.js'); const options = { contentBase: './dist', hot: true, host: 'localhost' }; webpackDevServer.addDevServerEntrypoints(config, options); const compiler = webpack(config); const server = new webpackDevServer(compiler, options); server.listen(5000, 'localhost', () => { console.log('dev server listening on port 5000'); });
ps:若是你使用的是webpack-dev-middleware能夠經過webpack-hot-middleware包來啓用HRM。
HRM是比較難掌握的,若是你點擊一下頁面上的按鈕會發現控制檯打印的仍是舊的那一句輸出。這是由於按鈕的事件仍是綁定在原來的printMe方法上。咱們須要使用module.hot.accept來更新這個綁定關係:
index.js
import _ from 'lodash'; import printMe from './print.js'; function component() { var element = document.createElement('div'); var 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); }) }
這只是一個例子,還有不少地方能讓人輕易犯錯,幸運的是有不少loader(下面會提到一些)可讓HRM變得平易近人一些。
藉助於style-loader,css的HRM變得很是簡單,當css模塊更新時style-loader會在後臺使用module.hot.accept來patch<style>標籤。
若是沒安裝的話這裏要安裝一下而且更新配置:
npm install --save-dev style-loader css-loader
webpack.config.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: { app: './src/index.js' }, devtool: 'inline-source-map', devServer: { contentBase: './dist', hot: true }, + module: { + rules: [ + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + } + ] + }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Hot Module Replacement' }), new webpack.HotModuleReplacementPlugin() ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } };
如今熱加載樣式表會和引入模塊同樣簡單:
project
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() { var element = document.createElement('div'); var 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); }) }
再把背景改成紅色你會發現瀏覽器會馬上更改而無需刷新:
styles.css
body { - background: blue; + background: red; }=
社區還有許多其餘 loader 和示例,可使 HMR 與各類框架和庫(library)平滑地進行交互……