64

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原创全栈干货+每周三深度技术文章


zz_jesse
976 声望63 粉丝

全栈开发进阶