簡單聊聊前端開發中的熱更新原理

clipboard.png

背景

前端項目開發過程中熱更新的機制大家都知道,不知道你在開發的時候是否做了這方面的配置。

相信接觸最多的就是 webpack 的熱更新,文件保存後頁面自動刷新,或者 css 自動更新,頁面的樣式在不刷新頁面的情況下就會更新。

還有就是模塊熱替換。

熱更新機制很好玩,能提升不少開發效率,但是隻是處於會用的階段不是我們的目的,我們應該適當的深入學習下,看看他背後的原理,一個是否思考過,一個是否能自己實現。

熱更新原理

咱們這裏主要說下怎樣自己實現一個熱更新,也就是文件更改了會自動刷新頁面,可以同步 pc 和 移動端,css 更改了可以不刷新頁面就應用最新的 css。

其實熱更新的原理並不複雜,或者說很簡單。

咱們一步一步的分析下。

本文不是要告訴你一些 api如何使用,而是利用架構的思維去分析和解決問題。

【分析】

  1. 文件內容變更了,瀏覽器是怎麼知道的呢?
  2. css 文件內容變更了,沒有刷新頁面 怎麼加載最新的內容呢?

只要解決了上面兩個問題,我們就算是完成了。因爲剩下得就是編碼了,這都好說。

【結果】

文件變更了,我怎樣通知瀏覽器?

  1. 瀏覽器和服務器保持着連接。 服務器有什麼事兒直接通過當前的鏈接告訴瀏覽器就可以了。

連接肯定是長連接,不然怎麼實時通信。

保持長連接有哪些方法呢? 輪詢?eventSorce? 都不夠好。

有麼有更好的方案呢?那就是 - websocket

瀏覽器和服務器先建立好鏈接,服務器就可以直接通知到客戶端了。這個時候無論是 pc 上還是手機上都可以隨時根據需要刷新或者加載資源。

  1. css 更新,css 本身是可以通過 dom 去操作的。瀏覽器只要知道是 css更新了,直接重新加載當前的 css 文件就可以了。

架構思維

咱們在重新捋捋這個架構。

  1. 服務器和瀏覽器通過 websocket 建立鏈接。
  2. 服務器和瀏覽器規定好消息的規則,是刷新頁面還是更新 css。

基本架構有了,其他的就是編碼實現了。

服務端使用 node 創建一個 ws 服務。

瀏覽器使用 websocket 創建一個鏈接和服務器進行鏈接。

雙方通過對應的 api 進行數據的操作。

代碼實現

本文只是講解下思路,並沒有實現文件的監聽,文件監聽後面會介紹。咱暫時先確定好兩個消息規則:

瀏覽器收到 命令爲:htmlFileChange ,此時瀏覽器刷新;

瀏覽器收到命令爲:cssFileChange,此時不刷新頁面,自動加載 css 文件;

具體代碼如下:

服務端:

//web-socket.js 創建 ws 服務
var ws = require("nodejs-websocket");//需要安裝這個包

module.exports = function(){
    return function () {
        console.log("重度前端提醒,開始建立連接...")

        var sessions = [];//存放每一個鏈接對象
        var server = ws.createServer(function (conn) {
            sessions.push(conn);//將新的鏈接對象存放在數組中

            conn.on("text", function (str) {
                console.log("收到的信息爲:" + str)
                sessions.forEach(item=>{
                    item.sendText(str) //所有客戶端都發送消息
                });

            });
            conn.on("close", function (code, reason) {
                console.log("關閉連接")
            });
            conn.on("error", function (code, reason) {
                console.log("異常關閉")
            });
        }).listen(6152)
        console.log("WebSocket建立完畢")
    }
}

//server.js http 服務代碼


let http = require('http');
let fs = require('fs');
let webSocket = require('./node/web-socket');

const BASEROOT = process.cwd();//獲得當前的執行路徑
//讀取 index.html內容
let getPageHtml = function () {
    let data = fs.readFileSync(BASEROOT+'/html/index.html');
   return data.toString();
}
//讀取 index.css內容
let getPageCss = function () {
    let data = fs.readFileSync(BASEROOT + '/html/index.css');
    return data.toString();
}

//node 端 開啓 ws 服務
webSocket()();

http.createServer(function (req, res) {//創建 http 服務

    let body = '',url = req.url;

    req.on('data', function (chunk) {
        body += chunk;
    });

    req.on('end', function () {
        //路由簡單處理 根據不同路徑輸出不同內容給瀏覽器
        if(url.indexOf('/index.css')>-1){
            res.write(getPageCss());
        }else{
            res.write(getPageHtml());
        }

        res.end();

    });

}).listen(6151);

console.log('重度前端提醒...... server start');

頁面截圖

clipboard.png

clipboard.png

客戶端

//index.html 佈局代碼省略

 const nick = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'aa', 'cc'];
    let index = 0;
    // Create WebSocket connection.
    const socket = new WebSocket('ws://10.70.69.191:6152');

    // Connection opened
    socket.addEventListener('open', function (event) {
        socket.send(navigator.userAgent);
    });

    // 監聽服務器推送的消息
    socket.addEventListener('message', function (event) {
        if (index > nick.length) {
            index = 0;//只是爲了每次輸出不同的暱稱,沒實際意義
        }

        var ele = document.createElement('div');
        ele.innerHTML = nick[index] + ':' + event.data;
        if (event.data === 'htmlFileChange') {
            //html 文件更新了 刷新當前頁面
            location.reload();
        }
        if (event.data === 'cssFileChange') {
            //css 文件更新了 刷新當前頁面
            reloadCss();
        }
        document.getElementById('content').append(ele);
        index += 1;
    });
    //重新加載 css
    function reloadCss() {
        var cssUrl = [],
            links = document.getElementsByTagName('link'),
            len = links.length;
        for (var i = 0; i < len; i++) {
            var url = links[i].href;
            document.getElementsByTagName('head')[0].appendChild(getLinkNode(url)); //創建新的 css 標籤
            document.getElementsByTagName('head')[0].removeChild(links[i]); //移除原有 css

        }
        console.log(document.getElementsByTagName('head')[0])

        function getLinkNode(cssUrl) {
            var node = document.createElement('link');
            node.href = cssUrl;
            node.rel = 'stylesheet';
            return node;
        }
    }

    document.getElementById('btn1').onclick = function () {
        socket.send(document.getElementById('message').value);
        document.getElementById('message').value = '';
    }

index.css 內容

 input {
      outline: none;
  }

  #content {
      height: 400px;
      width: 400px;
      border: solid 1px #ccc;
      color: red;
  }

代碼倒是次要的。解決問題的思路才重要。有了解決問題的架構思維,代碼實現都好說。

寫到這裏咱們還能順便實現一個羣聊。

本質就是服務器和瀏覽器怎樣實時通信,解決了這個問題,其他的都是小事兒。

這個技術實現還是比較簡單的。

另外對模塊熱更新和 websocket 原理有興趣的可以研究下,後面可能也會介紹。

總結

本文主要介紹

簡易版熱更新的原理;

熱更新實現思路和代碼實現;

架構思維:簡單的帶出架構思維的作用;

希望本文對你有用。

原創不易、請多鼓勵
自家觀點、歡迎打臉

代碼示例下載

https://github.com/bigerfe/ho...

作者:微信公衆號 - 重度前端 主筆:八門
歡迎關注 重度前端-每週5原創全棧乾貨+每週三深度技術文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章