Liveload:用Node.js实现页面热加载


我们在开发前端页面时,修改了样式表之后都会到浏览器手动刷新页面看修改后的效果。但是这样非常繁琐而且非常耗时。尤其是在各种设备上测试不同的浏览器时更是如此。而使用Node.js可以帮助我们实现页面热加载,点击一下保存,所有打开的页面都会刷新,是不是爽歪歪!

我们先来回顾一下前段开发的经典流程:

  1. 在多个浏览器打开页面;
  2. 寻找页面上需要调整的样式;
  3. 修改一个或多个样式;
  4. 手动刷新每个浏览器中的页面;
  5. 返回第2步。

其中第4步非常的繁琐,需要在每个浏览器中手动刷新页面,如果是在移动设备上测试,就更加耗时间了。

但是如果能完全去掉手动刷新这一步呢?想象一下,当在文本编辑器中保存样式表时,所有打开了那个页面的浏览器都会自动重新加载这个CSS样式表。这会帮节省大量时间。

下面我们就使用Socket.IO跟Node.js的fs.watchFile()fs.watch()函数配合,让这个想法变成现实。

关于Socket.IO大家可以看我这篇翻译至官网的文章了解一下。

fs.watchfile()fs.watch()是Node.js中两个监测文件的API(包括内容改动和名字、时间戳等的任何变化)。

  • fs.watchfile()通过轮询检测文件的变化,所以它更耗资源,而且反应有个时间差;但是它是跨平台的,在所有系统上的表现都一样。
  • fs.watch())是通过监听操作系统提供的各种”事件”(内核发布的消息)实现的,针对每个平台做了高度优化,但在每个平台上的表现是不同的,导致这个方法不能保证在所有平台上可用。

所以两种方法我都会讲到。

下面开始实现这个应用。

构建web服务器

我们使用Koa框架和Socket.IO结合在一起,构建一个静态Web服务器。

安装依赖

首先新建一个auto-reload文件夹,然后在文件夹下新建一个package.json文件,将下面内容复制到文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name": "liveload",
"version": "0.0.1",
"description": "Let the page automatically reload.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.0.0-alpha.7",
"koa-convert": "^1.2.0",
"koa-static": "^2.0.0",
"socket.io": "^1.7.1"
}
}

然后运行npm install安装依赖。

编写静态服务器

新建index.js文件并将下面内容复制到文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Koa = require('koa');
const convert = require('koa-convert');
const statc = require('koa-static');
const app = new Koa();
app
.use(async(ctx, next) => {
console.log(ctx.path);
await next();
})
// 将服务器设置为基本的静态文件服务器
.use(convert(statc(__dirname)));
app.listen('9000', () => {
console.log('Server running at post 9000.')
})

新建index.html文件并将下面内容复制到文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>liveload</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>This is our Awesome Webpage!</h1>
<div id="body">
<p>
If all the file is edited, then the server will send a message to the brower using Socket.IO telling it to refresh the page.
</p>
</div>
</body>
</html>

新建css文件夹,然后在该文件夹下新建style.css文件,写下如下样式:

1
2
3
h1{
color:red;
}

这样静态文件服务器就搭建好了。
因为我使用了async await语法,所以node 7.0以上的版本可以运行 node --harmony-async-await index 运行脚本,node 7.0以下的版本就需要用babel来转码了,安装以下依赖:

1
2
3
4
npm i -S babel-core
npm i -S babel-polyfill
npm i -S babel-preset-es2015
npm i -S babel-preset-stage-3

新建start.js文件,然后复制以下内容到文件:

1
2
3
4
5
require("babel-core/register")({
presets: ['stage-3', 'es2015']
});
require("babel-polyfill");
require("./index.js");

然后运行node start启动脚本。

启动脚本后在浏览器打开localhost:9000就可以看见页面了。
同时控制台会输出如下内容

1
2
/
/css/header.css

这样脚本就会获得请求的文件,然后对每个文件进行监听。

设置文件监听

下面就是设置文件监听的方法了。

创建一个监听函数用来监听文件:

1
2
3
4
5
6
7
8
9
10
11
12
const path = require('path');
const fs = require('fs');
let wathcers = {};
function createWatch(file) {
if (file === '/') file = 'index.html';
let absolute = path.join(__dirname, file);
if (wathcers[absolute] === true) return;
fs.watch(absolute, (e, filename) => {
console.log(e);
});
wathcers[absolute] = true;
}

上面是用fs.watch()方法写的,如果用fs.watchFile()写法如下:

1
2
3
4
5
fs.watchFile(absolute, (curr, prev) => {
if (curr.mtime.getTime() !== prev.mtime.getTime()) {
console.log(e);
}
});

因为直接访问localhost:9000是访问的index.html文件,所以解析到index.html;然后用path.join()获得文件的绝对路径,用fs.watch()监听。回调函数中的e是对文件做出的修改事件,filename是文件名。如果对文件进行了修改,那么就会输出e为:change

为了防止重复监听,定义一个wathcers对象,每监听一个文件,就将这个文件放进对象中,将值设为true。然后前段请求的文件判断有没有监听,如果已监听就返回,不再重复监听。

接着修改第一个中间件如下:

1
2
3
4
5
6
app
.use(async(ctx, next) => {
let file = ctx.path;
await next();
createWatch(file);
})

这样当你访问完了localhost:9000之后,对index.htmlstyle.css做修改,都会在控制台输出:change

用Socket.IO重载

下面就是用Socket.IO向页面发送文件修改过的消息,让页面重载了。

首先引入socket.io并整合到服务器上:

1
2
3
4
5
6
7
const socket = require('socket.io');
const app = new Koa();
const server = http.Server(app.callback());
const io = socket(server);
server.listen('9000', () => {
console.log('Server running at post 9000.')
});

然后在监听函数里修改:

1
2
3
4
5
6
fs.watch(absolute, (e, filename) => {
if (e === 'change') {
console.log(e);
io.sockets.emit('reload');
}
});

同时在index.html<head>标签里加上如下代码:

1
2
3
4
5
6
7
8
9
<script src="/socket.io/socket.io.js"></script>
<script>
window.onload = function() {
const socket = io();
socket.on('reload', () => {
window.location.reload();
})
}
</script>

好了,重启脚本然后试验了,比如说在打开localhost:9000后修改css文件,就会看到保存后页面就会刷新。

全部代码

  • index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const fs = require('fs');
const Koa = require('koa');
const http = require('http');
const path = require('path');
const convert = require('koa-convert');
const statc = require('koa-static');
const socket = require('socket.io');
const app = new Koa();
const server = http.Server(app.callback());
const io = socket(server);
app
.use(async(ctx, next) => {
let file = ctx.path;
await next();
createWatch(file);
})
// 将服务器设置为基本的静态文件服务器
.use(convert(statc(__dirname)));
server.listen('9000', () => {
console.log('Server running at post 9000.')
});
let wathcers = {};
function createWatch(file) {
if (file === '/') file = 'index.html';
let absolute = path.join(__dirname, file);
if (wathcers[absolute] === true) return;
fs.watch(absolute, (e, filename) => {
if (e === 'change') {
console.log(e);
io.sockets.emit('reload');
}
});
wathcers[absolute] = true;
}
  • index.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>liveload</title>
<link rel="stylesheet" href="/css/style.css">
<script src="/socket.io/socket.io.js"></script>
<script>
window.onload = function() {
const socket = io();
socket.on('reload', () => {
window.location.reload();
})
}
</script>
</head>
<body>
<h1>This is our Awesome Webpage!</h1>
<div id="body">
<p>
If all the file is edited, then the server will send a message to the brower using Socket.IO telling it to refresh the page.
</p>
</div>
</body>
</html>
  • /css/style.css:
1
2
3
h1{
color:blue;
}

好了,打完收工。

TOOl

这里有我做好的一个热加载的npm包:liveload-cli;

使用方法:

全局安装:

1
$ npm install -g liveload-cli

使用:

直接在项目目录下代开命令行,然后输入llc;浏览器访问localhost:9000加上文件名就可以了,默认是根目录index.html

默认通过9000端口访问,也可以设置端口号:

  • win:set PORT=端口号;
  • linux:PORT=端口号;

然后你就可以解放了,再也不用手动刷新了,尽情享受码代码的乐趣吧。

项目持续更新中。。。

文章目錄
  1. 1. 构建web服务器
    1. 1.1. 安装依赖
    2. 1.2. 编写静态服务器
  2. 2. 设置文件监听
  3. 3. 用Socket.IO重载
  4. 4. 全部代码
  • TOOl
  • |