如何在项目中实现热更新(中)

如何在项目中修改代码后实现热更新

上一篇文章说的是一个大体的概括,本篇主要分享一些node层面实现的细节。

工具源码EHU(esl-hot-update)

如何使用

npm install -g ehu(mac下需要sudo,windows下需要管理员权限)

在原来执行edp webserver start命令的路径 执行 ehu(不再需要执行 edp webserver start)

原来端口号8848修改为8844(原8848依旧可以使用,但不支持热更新)

首先使用的方式很简单,为此特意将工具打包到npm上,以后就算有升级,仅仅需要大家update即可。

另外从使用角度,也尽量集成化(一句命令行即可),避免为了这个工具的使用而做太多额外的事情。

依赖的框架

1
2
3
4
5
6
7
8
9
"dependencies": {
"async": "^1.5.0",
"commander": "^2.9.0",
"express": "^4.13.3",
"express-http-proxy": "^0.6.0",
"lodash": "^3.10.1",
"socket.io": "^1.3.7",
"watch": "^0.16.0"
}

几个必要的
watch——监听文件变化
socket.io——和浏览器的实时通讯
express——搭建一个服务
express-http-proxy——代理
commander——便于自己写node命令

工具类:
asynclodash

框架的思想

先看看昨天对于这个工具提出的几个要求

  1. esl必然是需要修改的,但是如何对开发人员透明?首先是不能让大家都做这种修改。
  2. 页面中也必须加入socket.io支持,那么我们如何在不影响其他人员开发的情况下加入?
  3. 我们做的属于beta版本,如何选择性的使用?ehu工具和以前的开发模式随意切换?
  4. 安装方便,能否只是作为一个工具,即插即用,不需要繁琐的配置?

对于1和2,我们其实是需要修改/添加一些代码的,但是代码都不希望提交到项目的开发环境,因为这些代码生成环境完全不需要。

所以我们的解决方案是:拦截,改写(偷梁换柱)

举个例子,当我们需要对esl做一些改造时,我们处理方式是当路由指向esl.js时,我们换成另外一个esl-ehu.js(esl-ehu.js是对esl.js改造后的)返回去,这样就对开发环境的代码透明了。

socket.io的支持也是同理,我们可以在返回html时,改写html的代码,加入对于socket.io的引入。

上面的思路其实来源于之前项目构建打包。

对于3,我们希望在使用工具时,任然能很快切换到以前模式,这样做兼容的目的是希望工具更有竞争力,能吸引大家使用。

我们的解决方案是:内部实现一个子线程,端口号依然是以前的,而且访问这个端口,就绕过了这个工具。

对于子线程child_process,我们还遇到一个问题,就是子线程跑系统的时候,经常挂掉,今天刚刚找到一个解决方案,后面会单开一个文章讲这个坑。

对于4,其实就是使用npm方式

技术细节

第一步:搭建一个新服务作为底层,去托管住我们现在edp服务,新服务上有一个路由配置,对于我们需要处理的,拦截。对于不用处理的直接代理给edp

代码参考

1
2
3
4
5
6
7
8
9
10
11
12
var mid = express();
mid.all('*', httpProxy(config.defaultServer, {
// 先走特殊规则,否则就代理到默认web server
filter: function(req, res) {
return !ruleRoute(req, res);
},
forwardPath: function(req, res) {
return URL.parse(req.url).path;
}
}));
// 由express-http-proxy托管路由
app.use('/', mid);

ruleRoute就是一些拦截处理

在此之前,启动下子进程

1
2
3
4
5
6
var child = require('child_process');
var cli = child.exec(defaultServerCLI);
cli.stdout.on('data', function (log) {
!isServerStarted && (cb(null, log));
isServerStarted && console.log(log);
});

此处有坑,后面单开文章描述

第二步: 因为上面拦截后的返回的文件已经支持socket.io,esl等底层已经修改了,所以下面是需要去监听文件通知浏览器做对应处理。

1
2
3
4
5
6
7
// 启动socket.io服务
io = require('socket.io')(server);
io.on('connection', function (socket) {
socket.emit('hello');
});
// 监视文件改动
initWatch();

第三步: 做一些集成工作

1
2
3
4
5
6
program
.version('0.0.6')
.usage('[options]')
.option('-p, --port <n>', 'Set the port', setPort)
.option('-n, --noServerCLI', '...', noServerCLI)
.parse(process.argv);

集成到node命令中

第四步: 默认配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
// 默认的服务器
defaultServer: 'http://127.0.0.1:8848',
// 默认的服务器启动命令
defaultServerCLI: 'edp webserver start',
// 从服务器根目录到需要监控的文件夹中间path
baseDir: 'nirvana-workspace',
// hot update 需要watch的文件夹(不包括baseDir)
watchDirs: 'src',
// 入口文件(不包括baseDir)
indexHTML: 'main.html',
// ehu启动端口号(不可与默认的服务器端口号冲突)
port: 8844
};

源码中有很多逻辑是处理配置的

最后一篇预告

分享一下esl等基础框架的改造,和浏览器监听到通信后的修改。

微信公众号

前端修炼