HMR 原理及应用
模块(Module)
在模块化编程中,开发者将程序分解成离散功能块,并将其称为模块。精心编写的模块提供了可靠的抽象能力和封装界限,应用程序中的模块都有明确的目的。
Node.js 从成型之初就支持模块化。而在 web 模块化到来之前,存在着种类繁多的 JavaScript 模块化工具。webpack 将模块的概念应用于项目中的任何文件。
webpack 用过 loader 可以支持各种语言和预处理器编写模块。loader 描述了如何处理非 JS 模块,并且在 bundle 中引入这些依赖。
模块解析规则
使用 enhanced-resolve
, webpack 可以解析三种路径。
绝对路径
import '/home/me/file'
import 'c:\\Users\\me\\file'
我们已经有了文件的绝对路径,所以不需要额外的解析 (不建议使用)。
相对路径
import '../src/file1'
import './file2'
在这种情况下,import
或 require
发生在源文件路径上下文路径 (context directory)。context directory 加上相对路径以供模块的使用。
解析相对路径
- 查找相对当前模块的路径下是否有对应文件或文件夹
- 是文件则直接加载
- 是文件夹则继续查找文件夹下的 package.json 文件
- 有 package.json 文件则按照文件中
main
字段的文件名来查找文件 - 无 package.json 文件或无
main
字段则查找index.js
文件
模块路径
import 'react'
import 'module/lib/file'
查找当前文件目录下,父级目录及以上目录下的 node_modules
文件夹,看是否有对应名称的模块。
在 webpack 配置中,和模块路径解析相关的配置都在 resolve
字段下。
module.exports = {
resolve: {
// ...
}
}
HMR
HMR(Hot Module Replacement) 即为模块热更替。在这个概念之前,我们使用过 Hot Reloading, 当代码发生更新时通知浏览器刷新页面,以避免频繁的手动刷新影响开发效率。HMR 可理解为加强版的 hot reloading,但不用刷新整个页面,只是局部替换掉部分模块代码而使其生效,可以看到变化后的效果。所以,HMR 的使用既避免了频繁手动刷新页面,也减少了刷新页面时的等待。
HMR 插件在 webpack-dev-server 中是内置的。只需要设置 webpack-dev-server 的相应参数即可。
- 将 devServer 的 hot 参数设为 true
- 引入 webpack.HotModuleReplacementPlugin() 和 webpack.NamedModulesPlugin()
- 在文件入口处引入 module.hot.accept, 监听文件的变化
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
},
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')
}
};
注意,我们还添加了 NamedModulesPlugin
,以便更容易查看要修补(patch)的依赖,显示相对路径的模块。在起步阶段,我们将通过在命令行中运行 npm start 来启动并运行 dev server。
修改 index.js 文件,以便当 print.js 内部发生变更时可以告诉 webpack 接受更新的模块。
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;
element.appendChild(btn);
return element;
}
let element = conponent()
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()
document.appendChild(element)
})
}
设置好 style-loader 和 css-loader 之后,也可使样式文件具备 HMR 功能。
HMR 运行原理
webpack 内部运行时,会维护一份用于管理构建代码时各个模块之间交互的表数据 manifest
,其中包括入口代码文件和构建出来的 bundle 文件的对应关系。
当根据前面的步骤启动支持 HMR 的 webpack-dev-server ,然后打开浏览器可以看到。
开启了 hot 功能的 webpack 会往我们应用的主要代码中添加 WS 相关代码,用于和服务器保持连接,等待更新动作。
当你配置了 HMR 的插件时,会往应用代码中添加 HMR 运行时的代码,主要用于定义代码模块应用更新时的 API。
当有更新时,webpack-dev-server 发送更新信号给 HMR 运行时,然后 HMR 再请求所需要的更新数据,请求的更新数据没有问题的话就应用更新。
在日常开发中,我们需要更多的工具来帮助我们实现 HMR 的接口,避免编写过多 HMR 需要的代码。例如,React 在组件代码更新时可能需要触发重新 render 来实现实时的组件展示效果,官方提供了一些现有的工具,需要的可以参考一下:hot module replacement tools。