webpack 快速構建 React 學習環境(2)-- 熱更新

螞蟻哈哈哈發表於2018-06-29

上一篇文章 《webpack 快速構建 React 學習環境(1)》中介紹了構建一個最簡單開發環境,這裡接著完善這個開發環境,讓它用起來更加的趁手。

看著篇文章前請先看 《webpack 快速構建 React 學習環境(1)》

本小結內容對應構建的專案原始碼:github.com/wewin11235/…,倉庫的 stage2 分支

文章同步釋出在個人部落格站點

上一篇中構建的開發環境存在的問題

上一節搭建的開發環境不能熱載入,每次檔案改動後都需要重新編譯,手動重新整理頁面。雖然使用 webpack --watch 命令在檔案變化後能重新編譯,但是仍需要手動重新整理頁面。webpack --watch 的方式還有個缺點,每次都是重新編譯生成新的檔案到 build 目錄下, 檔案多的時候這個編譯過程就會慢。

webpack-dev-server 配合 HRM 可以構建一個完美的開發環境,改動儲存後自動編譯,無需手動重新整理頁面。

使用 webpack-dev-server

webpack-dev-server 主要是啟動了一個使用 express 的 Http 伺服器。它的作用主要是用來伺服資原始檔。此外這個Http伺服器和client使用了websocket通訊協議,原始檔案作出改動後,webpack-dev-server 會實時的編譯. webpack-dev-server 的時時編譯並不會輸出到 webpack.config.js 中指定的出口,編譯生成的檔案在記憶體中,也有效的提高了編譯效率。

安裝與配置:

npm i webpack-dev-server -D
複製程式碼

安裝完成後: ./node_modules/.bin/webpack-dev-server 便可啟動 server


ℹ 「wds」: Project is running at http://localhost:8081/
ℹ 「wds」: webpack output is served from /./
⚠ 「wdm」: Hash: b2af4145bdae26f19266
複製程式碼

dev server 被啟動在了 8081 埠, server 會預設找專案主目錄下的 index.html 檔案作為服務的根頁面。 由於我們編譯後的服務入口檔案 index.html 放在 build 目錄下,所以要對 webpack-dev-server 做一下配置:

webpack.config.js 增加如下配置:

devServer: {
    contentBase: path.join(__dirname, 'build')
}
複製程式碼

此時 webpack.config.js :

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].[hash:8].js',
    publicPath: '/',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader'
        },
      },
    ],
  },
  plugins: [new HtmlWebpackPlugin({
    filename: 'index.html',
    template: 'src/index.html'
  })],
  devServer: {
    contentBase: path.join(__dirname, 'build'),
  }
};
複製程式碼

這樣就可以訪問 http://localhost:8081 檢視啟動的服務。

此時 dev server 雖然啟動,但嘗試修改 index.js 內容可以發現頁面並不能自動重新整理,webpack dever server 的自動重新整理有兩種模式 Iframe 和 inline 兩種模式,這裡用 inline 模式,修改配置如下:

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].[hash:8].js',
    publicPath: '/',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader'
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'src/index.html'
    })
  ],
  devServer: {
    contentBase: path.join(__dirname, 'build'),
    port: 9000,   // 指定服務啟動在 9000 埠
     inline: true,  // inline 模式啟動
     open: true  // 執行webpack-dev-server 後自動開啟瀏覽器
  }
};
複製程式碼

上面的配置,當檔案有更新,你會發現瀏覽器重新整理了,配置 HMR 可以實現區域性熱替換,不用整個瀏覽器重新整理。HMR 的配置請參考 [hot-module-replacement] (webpack.docschina.org/guides/hot-…)。 react 框架下還需要用到 react-hot-loader

開啟 HotModuleReplace

改動有以下幾處: 新增 src/print.js 檔案

export default function printMe() {
  console.log("Updating print.js...");
}
複製程式碼

修改 webpack.config.js 檔案,新增 hot 配置:

const path = require('path');
const webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].[hash:8].js',
    publicPath: '/',
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader'
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'src/index.html'
    }),
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    contentBase: path.join(__dirname, 'build'),
    port: 9000,
    open: true,  // HMR 支援
    hot: true
  }
};
複製程式碼

src/index.js 檔案如下:

import React from 'react';
import ReactDOM from 'react-dom';
import { hot } from 'react-hot-loader'

class HelloReact extends React.Component{
  constructor(props) {
    super(props);
  }

  render(){
    return( <div>Hello  React</div>);
  }
}

export default hot(module)(HelloReact);

ReactDOM.render(<HelloReact />, document.getElementById('root'));

if (module.hot) {
  module.hot.accept('./print.js', function(){
      console.log("Accepting the updated printMe module!");
      printMe();
  })
}
複製程式碼

React 框架下熱更新支援(react-hot-loader)

安裝 react-hot-loader

npm i react-hot-loader -D
複製程式碼

修改 .babelrc 如下

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }],
    "@babel/preset-react"
  ],
  "plugins": ["react-hot-loader/babel"]  //新增 ‘react-hot-loader/babel’ 這個外掛支援
}
複製程式碼

在使用的時候改變下 React 元件的 export 的方式,如我們的 src/index.js 檔案修改為如下:

import React from 'react';
import ReactDOM from 'react-dom';
import { hot } from 'react-hot-loader';  // 引入 hot 模組

class HelloReact extends React.Component{
  constructor(props) {
    super(props);
  }

  render(){
    return( <div>Hello  React</div>);
  }
}

export default hot(module)(HelloReact);  //修改 export 時候的方式

ReactDOM.render(<HelloReact />, document.getElementById('root'));

if (module.hot) {
  module.hot.accept('./print.js', function(){
      console.log("Accepting the updated printMe module!");
      printMe();
  })
}
複製程式碼

此時執行 ./node_modules/.bin/webpack-dev-server --mode=development ,再嘗試修改 index.js 內容你會發現頁面更新了,但是瀏覽器並沒有重新整理。

這樣我們就構建了一個自動編譯,自動更新的 React 的開發環境。

重構示例 react-demo

這個重構和開發環境搭建無關,只是在引入了 react-hot-loader 後,會發現現在的 src/index.js 此時顯得很不優雅,這給因為 ReactHello 作為一個元件本就應該抽取為一個獨立的檔案:

新建檔案:src/ReactHello.js:

import React from 'react';
import { hot } from 'react-hot-loader'

class HelloReact extends React.Component{
  constructor(props) {
    super(props);
  }

  render(){
    return( <div>Hello React</div>);
  }
}

export default hot(module)(HelloReact);
複製程式碼

調整 src/index.js:

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

ReactDOM.render(<HelloReact />, document.getElementById('root'));

if (module.hot) {
  module.hot.accept('./print.js', function(){
      console.log("Accepting the updated printMe module!");
      printMe();
  })
}
複製程式碼

這樣看著就舒服多了。

文章配合使用的 Demo 地址:github.com/wewin11235/… 倉庫的 stage2 分支

相關文章