HMR原理及应用

hot module replacement

Posted by Ericteen on March 11, 2018

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'

在这种情况下,importrequire 发生在源文件路径上下文路径 (context directory)。context directory 加上相对路径以供模块的使用。

解析相对路径

  1. 查找相对当前模块的路径下是否有对应文件或文件夹
  2. 是文件则直接加载
  3. 是文件夹则继续查找文件夹下的 package.json 文件
  4. 有 package.json 文件则按照文件中 main 字段的文件名来查找文件
  5. 无 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 ,然后打开浏览器可以看到。

console内容

开启了 hot 功能的 webpack 会往我们应用的主要代码中添加 WS 相关代码,用于和服务器保持连接,等待更新动作。

当你配置了 HMR 的插件时,会往应用代码中添加 HMR 运行时的代码,主要用于定义代码模块应用更新时的 API。

当有更新时,webpack-dev-server 发送更新信号给 HMR 运行时,然后 HMR 再请求所需要的更新数据,请求的更新数据没有问题的话就应用更新。

HMR原理

在日常开发中,我们需要更多的工具来帮助我们实现 HMR 的接口,避免编写过多 HMR 需要的代码。例如,React 在组件代码更新时可能需要触发重新 render 来实现实时的组件展示效果,官方提供了一些现有的工具,需要的可以参考一下:hot module replacement tools