热更新探究

微信扫一扫,分享到朋友圈

热更新探究

我们经常使用通过webpack搭建的脚手架,比如vue-cli来开发应用,当代码修改时页面直接更新,还有我经常在用的npm包:live-server静态服务器,代码修改后也直接更新视图,感觉很爽,所以想来探究一下里面是什么原理?是怎么工作的?

live-server

通过对live-server的探究,其热更新策略如下

live-server的源码较少,我就不贴了,通过对其的探究自己实现了一个热更新

核心

  1. 监听文件变化,通知ws透传客户端更新
  2. 更新策略

用到的npm包

faye-websocket

其用于在Node中轻松构建WebSocket服务器和客户端的类,因为node自己本身没有实现一个scoket模块

chokidar

封装 Node.js监控文件系统文件变化功能的库,为什么不用使用node本身的fs.watch和fs.watchFile?可以看这里

实现还是比较简单浅显的,直接贴出来吧!

服务端代码:

  1. 实现了一个简单静态服务器
  2. ws实例注册
  3. 文件监听执行ws发送客户端

npm包引入

const http = require('http')
const fs = require('fs')
const path = require('path')
const WebSocket = require('faye-websocket')
const chokidar = require('chokidar');
const mime = require('mime-types')
const networkInterfaces = require('os').networkInterfaces()
复制代码

简单静态服务器

// 获取注入脚本片段
const INJECTED_CODE = fs.readFileSync(path.resolve(__dirname, "injected.html"), "utf8");
const resolvePublic = (fileName) => path.resolve(`${__dirname}/public/${fileName}`);
// 静态服务器
const app = http.createServer((req, res) => {
if (req.url === '/') {
res.setHeader('content-type', 'text/html')
// 合并html入口文件和注入脚本返回
res.end(fs.readFileSync(resolvePublic('index.html')) + INJECTED_CODE)
} else {
try {
let file = req.url.split('?')[0]
res.setHeader('content-type', mime.lookup(file))
res.end(fs.readFileSync(resolvePublic(file)))
} catch (e) {
res.end()
}
}
})
复制代码

监听http upgrade事件,注入webscoket实例

// 注册WebSocket 实例
let clients = []
app.on('upgrade', (request, socket, head) => {
const ws = new WebSocket(request, socket, head)
ws.onopen = function() {
ws.send('connected');
}
ws.onclose = function() {
// 过滤掉当前关闭ws实例
clients = clients.filter(function (x) {
return x !== ws;
});
}
clients.push(ws)
})
复制代码

监听文件变化,向客户端发送信息

// 监听静态文件变化
const watcher = chokidar.watch(resolvePublic('/'))
watcher
.on("change", staticFileChange)
.on("add", staticFileChange)
.on("unlink", staticFileChange)
.on("addDir", staticFileChange)
.on("unlinkDir", staticFileChange)
.on("ready", function () {
console.log("Ready for changes");
})
.on("error", function (err) {
console.log("ERROR:".red, err);
});
function staticFileChange (changePath) {
const isCssFile = path.extname(changePath) === '.css'
console.log(`change file:${changePath}`)
clients.forEach(wx => {
wx.send(isCssFile ? 'refreshcss' : 'reload')
})
}
复制代码

启动

app.listen(1111, () => {
const address = networkInterfaces.en0[1].address || networkInterfaces.eth0[0].address // 获取内网ip
const notice = `
http://localhost:1111,
http://${address}:1111
`
console.log(notice)
})
复制代码

injected.html 注入文件

更新策略

  • 非css文件直接reload
  • css文件通过原链接后缀加时间戳重新请求css文件更新视图
<script type="text/javascript">
// <![CDATA[  <-- For SVG support
if ('WebSocket' in window) {
(function() {
function refreshCSS() {
var sheets = [].slice.call(document.getElementsByTagName("link"));
var head = document.getElementsByTagName("head")[0];
for (var i = 0; i < sheets.length; ++i) {
var elem = sheets[i];
head.removeChild(elem);
var rel = elem.rel;
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
var url = elem.href.replace(/(&|?)_cacheOverride=d+/, '');
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
}
head.appendChild(elem);
}
}
// var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
var address = 'ws://' + window.location.host + window.location.pathname + '/ws';
var socket = new WebSocket(address);
socket.onmessage = function(msg) {
if (msg.data == 'reload') window.location.reload();
else if (msg.data == 'refreshcss') refreshCSS();
else { console.log(msg.data); }
};
console.log('Live reload enabled.');
})();
}
// ]]>
</script>
复制代码

未完…待续…

台积电回应三星芯片追赶:有信心技术上持续领先

上一篇

k8s OOMkilled超出内存限制的容器

下一篇

你也可能喜欢

热更新探究

长按储存图像,分享给朋友