webpack學習之路(五)

熱模塊替換

  熱模塊替換(HMR)是webpack提供的最有用的功能之一,它讓各類模塊能夠在運行時更新而無需刷新,本篇主要注重於實現。css

  ps:HMR是爲開發模式設計的,也只能用於開發模式。html

啓用HRM

  啓用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.

經過Node.js API:

  當使用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變得平易近人一些。

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)平滑地進行交互……

  • React Hot Loader:實時調整 react 組件。
  • Vue Loader:此 loader 支持用於 vue 組件的 HMR,提供開箱即用體驗。
  • Elm Hot Loader:支持用於 Elm 程序語言的 HMR。
  • Redux HRM:無需 loader 或插件!只需對 main store 文件進行簡單的修改。
  • Angular HRM:No loader necessary! A simple change to your main NgModule file is all that's required to have full control over the HMR APIs.沒有必要使用 loader!只需對主要的 NgModule 文件進行簡單的修改,由 HMR API 徹底控制。
相關文章
相關標籤/搜索