加载中...

模块热替换 - React


正如在概念章节提到的,模块热替换(HMR)的作用是,在应用运行时,无需刷新页面,便能替换、增加、删除必要的模块。 HMR 对于那些由单一状态树构成的应用非常有用。因为这些应用的组件是 "dumb" (相对于 "smart") 的,所以在组件的代码更改后,组件的状态依然能够正确反映应用的最新状态。

下面的介绍是基于 Babel 和 React 的,但它们对于 HMR 并不是必需的。

T> 如果你想了解别的配置方式,可以告诉我们,或者更好的方式是,提一个 PR

项目配置

下面将会示范怎么在 Babel, React 和 PostCSS(使用 CSS 模块)的项目中配置 HMR。为此,你需要在你的 package.json 加上以下依赖,可以通过下面的命令来安装:

npm install --save-dev babel-core@6.13.2 babel-loader@6.2.4 babel-preset-es2015@6.13.2 babel-preset-react@6.11.1 babel-preset-stage-2@6.13.0 css-loader@0.23.1 postcss-loader@0.9.1 react-hot-loader@3.0.0-beta.6 style-loader@0.13.1 webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.0 

另外,你也需要安装 React:

npm install --save react@15.3.0 react-dom@15.3.0 

Babel 配置

你的 .babelrc 配置文件或许会和下面的配置相差无几:

{
  "presets": [
    ["es2015", {"modules": false}],
    // webpack现在已经支持原生的import语句了, 并且将其运用在tree-shaking特性上

    "stage-2",
    // 规定JS运用的语言规范层级
    // Stage 2 是 "草案", 4 是 "已完成", 0 is "稻草人(strawman)"。
    // 详情查看 https://tc39.github.io/process-document/

    "react"
    // 转译React组件为JS代码
  ],
  "plugins": [
    "react-hot-loader/babel"
    // 开启react代码的模块热替换(HMR)
  ]
} 

Webpack 配置

当然,有很多方法来设置你的 webpack ── 例如通过 API 配置,由单个或多个配置文件来配置,等等。下面是一个基本的配置,可以供你作为参考:

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: [
    'react-hot-loader/patch',
    // 开启react代码的模块热替换(HMR)

    'webpack-dev-server/client?http://localhost:8080',
    // 为webpack-dev-server的环境打包好运行代码
    // 然后连接到指定服务器域名与端口

    'webpack/hot/only-dev-server',
    // 为热替换(HMR)打包好运行代码
    // only- 意味着只有成功更新运行代码才会执行热替换(HMR)


    './index.js'
    // 我们app的入口文件
  ],
  output: {
    filename: 'bundle.js',
    // 输出的打包文件

    path: resolve(__dirname, 'dist'),

    publicPath: '/'
    // 对于热替换(HMR)是必须的,让webpack知道在哪里载入热更新的模块(chunk)
  },

  context: resolve(__dirname, 'src'),

  devtool: 'inline-source-map',

  devServer: {
    hot: true,
    // 开启服务器的模块热替换(HMR)

    contentBase: resolve(__dirname, 'dist'),
    // 输出文件的路径

    publicPath: '/'
    // 和上文output的"publicPath"值保持一致
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          'babel-loader',
        ],
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader?modules',
          'postcss-loader',
        ],
      },
    ],
  },

  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    // 开启全局的模块热替换(HMR)

    new webpack.NamedModulesPlugin(),
    // 当模块热替换(HMR)时在浏览器控制台输出对用户更友好的模块名字信息
  ],
}; 

上面的内容涵盖了 webpack 配置的方方面面,并不是全部都和 HMR 相关。这个 webpack 开发服务器的完整的文档能够让你对它了解更多,这些在 webpack.js.org 上的文章也应该一读。

这里有一个基本假设,便是你的 JavaScript 入口在 ./src/index.js,还有,你在使用 CSS 模块。

配置中的注释或许能够帮助你理解一二。有两个主要的部分值得一看: devServer 键和 entry 键。另外,HotModuleReplacementPlugin 是必须加到 plugins 数组中去的。

这里特别要提一下下面的两个模块:

  • entry 里的 react-hot-loader,是 React 配置 HMR 必不可少的模块。

  • 还有 NamedModulesPlugin,它的用处在于,能让你知道热重载时是哪个模块作出了变动。

Code

下面是和上面配置相关的代码:

// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';

import { AppContainer } from 'react-hot-loader';
// AppContainer is a necessary wrapper component for HMR

import App from './components/App';

const render = (Component) => {
  ReactDOM.render(
    <AppContainer>
      <Component/>
    </AppContainer>,
    document.getElementById('root')
  );
};

render(App);

// 模块热替换的 API
if (module.hot) {
  module.hot.accept('./components/App', () => {
    render(App)
  });
} 
// ./src/components/App.js
import React from 'react';
import styles from './App.css';

const App = () => (
  <div className={styles.app}>
    <h2>Hello, </h2>
  </div>
);

export default App; 
// ./src/components/App.css
.app {
    text-size-adjust: none;
    font-family: helvetica, arial, sans-serif;
    line-height: 200%;
    padding: 6px 20px 30px;
} 

事实上,上面的代码中最重要是引用 module 的那一部分代码。

  1. 如果我们设置了 devServer: { hot: true },webpack 会暴露 module.hot 给我们的代码;

  2. 因此,我们可以使用 module.hot 钩子函数为特定资源启用 HMR(这里是App.js)。这里最重要的 API 是 module.hot.accept,它指定如何处理对特定依赖的更改。

  3. 注意,因为 webpack 2 对 ES2015 模块有内置的支持,你不需要在 module.hot.accept 中重新引入你的根组件。要完成这项工作,你需要更改 Babel ES2015 在 .babelrc 的预设值:

    ["es2015", {"modules": false}] 

    与我们在 Babel 配置文件 中所配置的是一样的。注意,不仅仅只有模块热替换的场景需要禁用Babel模块插件。如果你不将此插件禁用,你可能会遇到许多其他的问题(查看 从webpack v1 迁移到 v2 和 webpack-tree-shaking)。

  4. 注意,如果你在webpack 2 配置文件中启用了 ES2015 模块,并且按照上文#3 的配置,修改了你的.babelrc 文件,你需要使用require命令,或者,创建两个.babelrc文件(查看问题 这里):

    • 一个文件放置在项目的根目录,并且加上配置: "presets": ["es2015"]
    • 另一个文件放置在webpack要构建代码的主目录。在这个例子里,放置的目录路径是src/

所以,在这种情景下,当 src/components/App.js 或者它的依赖文件被更改了, module.hot.accept 将会触发 render 方法,这意味着,因为 App.js 里面包含了对 App.css 的引用, 所以 render 方法同样会在 App.css 被修改的时候触发,

index.html

这个文件需要放置在你的项目根路径下的 dist目录,不然 webpack-dev-server 将因为缺少这个文件而无法运行。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example Index</title>
</head>
<body>
  <div id="root"></div>
  <script src="bundle.js"></script>
</body>
</html> 

Package.json

最后,让我们启动 webpack-dev-server 来打包我们的代码,看看 HMR 的运行效果吧。我们可以使用下面的 package.json 进入:

{
  "scripts" : {
    "start" : "webpack-dev-server"
  }
} 

运行 npm start。然后打开浏览器,在地址栏输入 localhost:8080。这时候应该在你的 console.log 中会看到下面的输出:

dev-server.js:49[HMR] Waiting for update signal from WDS…
only-dev-server.js:74[HMR] Waiting for update signal from WDS…
client?c7c8:24 [WDS] Hot Module Replacement enabled. 

接着你可以对 App.js 作出修改并保存。你的 console.log 应该会显示下面的信息:

[WDS] App updated. Recompiling…
client?c7c8:91 [WDS] App hot update…
dev-server.js:45 [HMR] Checking for updates on the server…
log-apply-result.js:20 [HMR] Updated modules:
log-apply-result.js:22 [HMR]  - ./components/App.js
dev-server.js:27 [HMR] App is up to date. 

我们可以看到,HMR 标记出了被修改文件的路径。这便是 NamedModules 插件所起的作用。

原文:https://webpack.js.org/guides/hmr-react/


还没有评论.