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

clipboard.png

背景

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

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

還有就是模塊熱替換。html

熱更新機制很好玩,能提高很多開發效率,可是隻是處於會用的階段不是咱們的目的,咱們應該適當的深刻學習下,看看他背後的原理,一個是否思考過,一個是否能本身實現。前端

熱更新原理

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

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

我們一步一步的分析下。webpack

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

【分析】github

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

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

【結果】

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

  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原創全棧乾貨+每週三深度技術文章

相關文章
相關標籤/搜索