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

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

本篇文章是最后一篇,主要讲一下在浏览器端的一些实现。和浏览器热更新的细节。

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

浏览器端依赖

socket.io——浏览器端仅仅依赖socket这个去和服务端通信

通信逻辑

1
2
3
4
5
6
7
8
9
// 建立连接
socket.on('hello', function () {
log(getLogMsgPrefix(), 'HotUpdate已启动!');
});
// 检测到文件改动
socket.on('hotUpdate', function (file) {
// log(getLogMsgPrefix(), '检测到文件改动', file);
// ....处理文件修改后对应热更新逻辑
});

对css/less更新的处理

这个原理比较简单,页面监听到样式的修改,重新加载一次样式即可,简单的覆盖。

但是存在一个潜在问题,因为样式是简单的覆盖,所以,如果修改是删除了样式,是无法生效的。

举例:
修改前:

1
2
3
4
5
6
display: none;
overflow: hidden;
position: relative;
background: #FFFFFF;
border: 1px solid #E8E8E8;
margin-top: 20px;

修改后:

1
2
3
4
display: none;
overflow: hidden;
position: relative;
background: #FFFFFF;

删除的bordermargin-top其实是没有生效的

这个也是后期需要解决的一个问题。

对模板更新的处理

目前项目中使用的是tpl的模板引擎。

现在就遇到一个问题,在热更新时,模板引擎其实是重复加载模板的,那么就涉及到重复加载是否后面的会覆盖前面问题。

查看加载模板的源码后,发现根据配置有三个选择,覆盖忽略报错, 我们业务中使用的配置是遇到重复后会报错处理,所以我们需要在不修改业务默认属性的情况下,添加一些逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// [esl-hot-update] 重新加载需要覆盖
window.EHU_HOT_UPDATE_OPTIONS
&& window.EHU_HOT_UPDATE_OPTIONS.etpl.isOverride
&& (namingConflict = 'override');
switch (namingConflict) {
/* jshint ignore:start */
case 'override':
engine.targets[name] = target;
context.targets.push(name);
case 'ignore':
break;
/* jshint ignore:end */
default:
throw new Error('Target exists: ' + name);
}

window.EHU_HOT_UPDATE_OPTIONS.etpl.isOverride这个是修改后自己实现的控制配置修改的逻辑。

然后这个文件加入到服务端的路由中,请求时替换。

对js更新的处理

这里逻辑比较复杂,因为需要修改底层的AMD模块加载的逻辑。

js没有模板那么简单,不是直接覆盖,因为在AMD模式中,每一个文件,都是被上一个文件调用执行的结果。

所以我们处理的逻辑是不仅需要重新加载修改的文件,并且递归所有直接或者间接调用他的文件,全部重新加载。

所以从上面的特点可以看出,这个工具目前阶段主要适用于业务模块的开发,因为业务的依赖不会特别深,对于dep中的核心文件修改,就不是很合适,一旦文件比较底层,热跟新是重新加载的模块也会非常多。

另外也有很多其他的坑,还在不断优化中。

总结

这次实践其实就是业务中遇到的问题(系统太庞大,调试太麻烦),如何解决问题,如何把解决的思路变成一个解决方案,分享给团队。

因为自己解决了,和形成一个解决方案还是有非常大的差别的,例如我们在形成方案的过程中,就尝试了很多新东西,踩了很多坑。

目前还有个坑就是chrome浏览器,调试的Source资源时,如果一个资源重复加载,内存中会更新,但是对应的资源没有更新,导致断点时,映射不对(断点失效),目前暂时的解决方案是,每次请求时添加时间戳,让Source映射的资源强制更新。这个可以正常断点,但是断点没有记忆功能(坑啊,因为文件变了)。

微信公众号

前端修炼