天天看點

webpack系列4之webpack-dev-server的inline/frame模式

作者:IT小亮

1.本章概述

在本章節,我們将簡要的對webpack-dev-server的基本使用做一個示範。通過這個章節,你可以學會如何在自己的項目中使用webpack-dev-server。

2.webpack-dev-server使用

第一步:安裝webpack-dev-server

npm install webpack-dev-server -g           

第二步:在項目根目錄下配置webpack.config.js

var path = require("path");
module.exports = {
  entry: {
    app: ['./src/main.js']
  },
  output: {
    path: path.resolve(__dirname, "public"),
    publicPath: "",
    filename: "bundle.js"
  }
};           

其中裡面的各項配置,通過前面的章節你應該能夠了解,此處不再贅述,如果不懂,可以仔細閱讀前面的章節。

第三步:配置package.json中的scripts部分

"scripts": {
    "build": "webpack",
    "start": "webpack-dev-server --inline --hot --port 3000 --content-base public",
    "test": "echo \"Error: no test specified\" && exit 1"
  },           

其中對于script部分不太了解的可以檢視我寫的package.json中的scripts部分深入講解。通過配置scripts你可以使用如下指令:

npm start
//或者
npm run start           

來替換掉:

webpack-dev-server --inline --hot --port 3000 --content-base public           

當然,如果你不想做替換,依然可以在目錄下運作你全局安裝的webpack-dev-server指令。此時需要的所有配置都已經完成了,啟動你的npm run start就會啟動一個Express伺服器,端口号是3000,同時支援HMR,contentBase為我們指定的/public。

3.webpack-dev-server的inline模式與iframe模式

上面的打包執行個體中你看到我在cli中傳入了*--inline*,表示這是内聯的打包方式,那麼什麼是内聯的打包方式呢?

3.1 iframe模式

我們首先看看Express伺服器是如何處理iframe模式的:

app.get('/webpack-dev-server/*', (req, res) => {
    res.setHeader('Content-Type', 'text/html');
    fs.createReadStream(path.join(__dirname, '..', 'client', 'live.html')).pipe(res);
  });           

是以當我們在URL中加入webpack-dev-server的路徑以後,我們就會傳回live.html,我們再看看live.html的内容:

<!DOCTYPE html>
<html>
 <head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
  <script type="text/javascript" charset="utf-8" src="/__webpack_dev_server__/live.bundle.js"></script>
 </head>
 <body> 
 </body>
</html>           

其直接傳回了*/webpack_dev_server/live.bundle.js*。我們看看這個請求Express伺服器傳回的資源内容:

app.get('/__webpack_dev_server__/live.bundle.js', (req, res) => {
    res.setHeader('Content-Type', 'application/javascript');
    fs.createReadStream(path.join(__dirname, '..', 'client', 'live.bundle.js')).pipe(res);
  });           

是以我們最終傳回的還是client/live.bundle.js,我們就來看看live.bundle.js的具體内容。首先它會在頁面中建立一個iframe并注冊了用戶端代碼:

const onSocketMsg = {
    hot() {
      hot = true;
      iframe.attr('src', contentPage + window.location.hash);
    },
    invalid() {
      okness.text('');
      status.text('App updated. Recompiling...');
      header.css({
        borderColor: '#96b5b4'
      });
      $errors.hide();
      if (!hot) iframe.hide();
    },
    hash(hash) {
      currentHash = hash;
    },
    'still-ok': function stillOk() {
      okness.text('');
      status.text('App ready.');
      header.css({
        borderColor: ''
      });
      $errors.hide();
      if (!hot) iframe.show();
    },
    ok() {
      okness.text('');
      $errors.hide();
      reloadApp();
    },
    warnings() {
      okness.text('Warnings while compiling.');
      $errors.hide();
      reloadApp();
    },
    errors(errors) {
      status.text('App updated with errors. No reload!');
      okness.text('Errors while compiling.');
      $errors.text(`\n${stripAnsi(errors.join('\n\n\n'))}\n\n`);
      header.css({
        borderColor: '#ebcb8b'
      });
      $errors.show();
      iframe.hide();
    },
    close() {
      status.text('');
      okness.text('Disconnected.');
      $errors.text('\n\n\n  Lost connection to webpack-dev-server.\n  Please restart the server to reestablish connection...\n\n\n\n');
      header.css({
        borderColor: '#ebcb8b'
      });
      $errors.show();
      iframe.hide();
    }
  };
  socket('/sockjs-node', onSocketMsg);           

這樣,當webpack的資源發生變化以後可以通過websocket通知到我們這個首頁面。我們看看首頁面最重要的一個方法,即reloadApp方法,方法内容如下:

function reloadApp() {
    //如果開啟了HMR功能
    if (hot) {
      status.text('App hot update.');
      try {
        iframe[0].contentWindow.postMessage(`webpackHotUpdate${currentHash}`, '*');
      } catch (e) {
        console.warn(e); // eslint-disable-line
      }
      iframe.show();
    } else {
      status.text('App updated. Reloading app...');
      header.css({
        borderColor: '#96b5b4'
      });
      try {
        let old = `${iframe[0].contentWindow.location}`;
        if (old.indexOf('about') === 0) old = null;
        iframe.attr('src', old || (contentPage + window.location.hash));
        if (old) {
            //強制重新整理
          iframe[0].contentWindow.location.reload();
        }
      } catch (e) {
        iframe.attr('src', contentPage + window.location.hash);
      }
    }
  }
});           

這個首頁面接受到事件以後,通過postMessage将目前打包的hash值發送到我們的内部的iframe。如果開啟了HMR,那麼iframe會檢查資源更新,如果沒有開啟HMR,那麼強制iframe進行重新整理。上面講了很多原理的知識,我們下面給出一個日常執行個體:

我們的頁面被嵌套在一個iframe中,當資源改變的時候會重新加載。隻需要在路徑中加入webpack-dev-server就可以了,不需要其他的任何處理:

http://localhost:8080/webpack-dev-server/index.html           

進而在頁面中就會産生如下的一個iframe标簽并注入css/js/DOM:

webpack系列4之webpack-dev-server的inline/frame模式

這個首頁面會請求live.bundle.js ,其中裡面會建立一個Iframe ,你的應用就被注入到了這個 iframe 當中。同時live.bundle.js中含有socket.io的client代碼,這樣它就能和 webpack-dev-server建立的 http server 進行 websocket 通訊了,并根據傳回的資訊完成相應的動作。

總之,因為我們的http://localhost:8080/webpack-dev-server/index.html通路的時候加載了live.bundle.js,其具有websocket的client代碼,是以當websocket-dev-server服務端代碼發生變化的時候會通知到這個頁面,這個頁面隻是需要重新重新整理iframe中的頁面就可以了。該模式有如下作用:

No configuration change needed.(不需要修改配置檔案)
Nice information bar on top of your app.(在app上面有information bar)
URL changes in the app are not reflected in the browser’s URL bar.(在app裡面的URL改變不會反應到浏覽器的位址欄中)
           

3.2 inline mode

webpack-dev-server的用戶端入口被添加到檔案中,用于自動重新整理頁面。其中在cli中輸入的是:

webpack-dev-server --inline --content-base ./build           

此時在頁面中輸出的内容中看不到插入任何的js代碼:

webpack系列4之webpack-dev-server的inline/frame模式

但是在控制台中可以清楚的知道頁面的重新編譯等資訊:

webpack系列4之webpack-dev-server的inline/frame模式

該模式有如下作用:

Config option or command line flag needed.(webpack配置或者指令行配置)
Status information in the console and (briefly) in the browser’s console log.(狀态資訊在浏覽器的console.log中)
URL changes in the app are reflected in the browser’s URL bar(URL的改變會反應到浏覽器的位址欄中).
           

每一個模式都是支援Hot Module Replacement的,在HMR模式下,每一個檔案都會被通知内容已經改變而不是重新加載整個頁面。是以,在HMR執行的時候可以加載更新的子產品,進而把他們注冊到運作的應用裡面。不管是inline模式還是iframe模式,我們都是通過socketjs來連接配接用戶端代碼和webpack-dev-server的服務端代碼,其原理都是一樣的。

3.本章小結

該本章節中,我們講解了如何在你的項目中使用webpack-dev-server來啟動一個Express伺服器,同時也分析了webpack-dev-server本身具有的inline模式和iframe模式。并深入了講解了iframe模式的原理,希望你能對webapck-dev-server有一個更加深入的了解。文中提到的示例代碼你可以點選這裡下載下傳。

繼續閱讀