很小伙伴或许和我一样,在平时写前端项目的时候都是使用网上现成的脚手架吧。在编写js,或者vue代码的时候,在浏览器端显示的界面,会及时刷新,也是处于好奇,想着自己是不是可以简单实现一下呢?说干就干,于是有了这篇文章
先来讲一下实现的原理
[“实际使用的脚手架框架中不仅仅有热更新,还有代码的压缩打包等功能,这里就仅仅实现热跟新的功能”]
运行测试服务器的时候会开启
http
服务与
webSockit
服务以及通过插件监听文件变化
http服务是用来访问测试页面的,
通过开启目录变化的监听,当目录文件发生变化的时候通过
webSockit
控制是否全局或是局部的刷新。
1. 首先来创建静态html文件
html这里用到了es6的对外部包的引用,需要
type="module"
标识,可以使用import命令加载其他模块。
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Vite App</title>
</head>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
2. 使用chokidar
插件来实现对文件变化监听
nodejs 里面有个fs.watch api。它可以监听文件变动,
使用示例:fs.watch(filename[, options][, listener])
// WebSocket 连接客户端实例
const wsClients=[];
.............
function fileChange(event, changePath) {
const refreshcss = path.extname(changePath) === '.css'&&event!='add'//判断刷新类型
console.log(`event: ${event} change file:${changePath}`)
wsClients.forEach(ws => {//遍历已连接的webSockit客户端执行更新
ws.send(refreshcss ? 'refreshcss' : 'reload')
chokidar.watch('src', {
persistent: true,
ignored: /(^|[\/\\])\../, // 忽略点文件
cwd: '.', // 表示当前目录
depth: 0 // 只监听当前目录不包括子目录
}).on('all', fileChange);//监听除了ready, raw, and error之外所有的事件
3. 使用Koa快速创建http服务
const fs = require('fs')
const path = require('path')
const Koa = require('koa')
const mime = require('mime-types')
const networkInterfaces = require('os').networkInterfaces()
const app = new Koa();
app.use(async ctx => {
const { request: { url } } = ctx
// 首页
if (url == '/') {
ctx.type = "text/html"
ctx.body = fs.readFileSync('./index.html', 'utf-8')
} else if (url.endsWith('.js')) {
try {
let file = ctx.req.url.split('?')[0]
ctx.type = mime.lookup(file)
ctx.body = fs.readFileSync(resolvePublic(file), 'utf-8')
//........
} catch (e) {
const PROT=3001;
server.listen(PROT, () => {
const ens = Object.keys(networkInterfaces)[0];
const address = networkInterfaces[ens][1].address || networkInterfaces[ens][0].address // 获取内网ip
const notice = `open:
http://localhost:${PROT},
http://${address}:${PROT}
console.log(notice)
4. 添加WebSocket服务
添加WebSocket服务有很多方法和相关的插件,这里就使用ws
库来实现吧。
//...
const WebSocket = require('ws');
//...
const wss = new WebSocket.Server({);
//...
wss.on('connection', function connection(ws) {
ws.send('connected');
wsClients.push(ws);//连接成功自动添加
ws.onclose = function () {
// 过滤掉当前关闭ws实例
wsClients = wsClients.filter(function (x) {
return x !== ws;
//...
5. http与WebSocket端口共用
为了实现能够让http与WebSocket公用端口,使用http
库进行结合
//...
const http = require('http')
//...
//...
const server = http.createServer(app.callback())
const wss = new WebSocket.Server({// 公用端口
server
//...
//...
//...
const PROT=3001;
server.listen(PROT, () => {
const ens = Object.keys(networkInterfaces)[0];
const address = networkInterfaces[ens][1].address || networkInterfaces[ens][0].address // 获取内网ip
const notice = `open:
http://localhost:${PROT},
http://${address}:${PROT}
console.log(notice)
6. 注入浏览器端的WebSocket交互客户端代码
虽然开启了WebSocket服务,但是前端需要能够连接上服务才能,所以需要往html页面或者是js中注入相关的脚本代码。
我就用简单粗暴的方式来演示一下:
//...
app.use(async ctx => {
const { request: { url } } = ctx
// 首页
if (url == '/') {
ctx.type = "text/html"
ctx.body = fs.readFileSync('./index.html', 'utf-8') + `
<script type="text/javascript">
(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.split("?")[0]
elem.href = url + '?_=' + (+new Date());
// 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;
var socket = new WebSocket(address);
socket.onmessage = function (msg) {
console.log(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>`
} else {
try {
let file = ctx.req.url.split('?')[0]
ctx.type = mime.lookup(file)
ctx.body = fs.readFileSync(resolvePublic(file), 'utf-8')
} catch (e) {