最近做了一些基于Egg的web项目,在开发阶段,一般会在本地安装nodemon这样的工具,从而当我们改变一个文件时,他可以监听到这个改动,然后重新启动进程,将数据从硬盘里重新加载到内存中,所以我们新的改动可以应用到应用程序中去。这里不免想到一个问题,如果不重新启动的话,我修改了一个文件,怎么去应用最近的改动呢?
- 首先先简单介绍一下require这个属性,当我们编写完Node.js代码后,被解释器执行时,会做一层包装,像下面那样
(function(module,exports,require,__dirname,__pathname ){
// your code ...
})
所以我们可以直接在任何地方引用require这个方法,除了用来引入模块以外,它上面还有一些额外的数据。我们先建立2个简单的文件,在同一个目录下
// entry.js
const lib = require('./lib');
const name = lib.getName();
console.log(name);
//lib.js
console.log('I got it');
const lib = {
name: 'kasol',
getName: function() {
return this.name;
}
};
module.exports = lib;
然后我们直接执行entry.js,结果很明显
//I got it
//kasol
- 接下来我们做一点点简单的改动
// entry.js
const lib = require('./lib');
const lib2 = require('./lib');
const lib3 = require('./lib');
const name = lib.getName();
console.log(name);
我在entry.js中,多次引用了lib,输出的结果会怎么样呢?
//I got it
//kasol
是的,“I got it” 始终只输出了一次,因为Node在加载执行完一个模块之后,会将它缓存下来,之后再有模块想要去引用它时,就会直接从缓存中返回,所以这个console只执行了一次。
- 从上面看来,就算我们能够在模块改动后,重新 require对应的文件,还是会从缓存中去取,那么也不能拿到最新的改动,所以要先解决怎么让他重新去加载的问题,这时候就要看上述提到的require.cache了。我们再来改改entry.js
//entry.js
const lib = require('./lib');
console.log(require.cache);
我们执行下,看看打印的结果
{
Module {
id: '/Users/zhongzeming/webpackDemo/test/lib.js',
exports: { name: 'kasolll', getName: [Function: getName] },
parent:
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/zhongzeming/webpackDemo/test/entry.js',
loaded: false,
children: [Array],
paths: [Array] },
filename: '/Users/zhongzeming/webpackDemo/test/lib.js',
loaded: true,
children: [],
paths:
[ '/Users/zhongzeming/webpackDemo/test/node_modules',
'/Users/zhongzeming/webpackDemo/node_modules',
'/Users/zhongzeming/node_modules',
'/Users/node_modules',
'/node_modules' ] } }
简单起见,我只贴出了lib模块的信息,可以看到上面这个cache里面是存在的,所以在第二次去require的时候,就去这里拿了。
- 所以我们再改改entry
//entry.js
const lib = require('./lib');
console.log(require.cache);
delete require.cache[require.resolve('./lib.js')];
console.log(require.cache);
这时候会发现,这个cache里面就不存在lib模块的信息了,上面的代码里面,有一个delete require.cache[require.resolve(’./lib.js’)],require.resolve就是返回这个模块的绝对路径,作为一个key值去cache里面把这个模块的信息给删去,之后再去require的话,就又会去重新加载这个模块了,最后我们把俩个文件都改改
如下
// entry.js
let lib = require('./lib');
const fs = require('fs');
const path = require('path');
fs.watch(path.join(__dirname, 'lib.js'), { encoding: 'utf-8' }, (eventType, filename) => {
delete require.cache[require.resolve('./lib.js')];
lib = require('./lib');
console.log(lib.getName());
});
// lib.js
console.log('I got it');
const lib = {
name: 'kasol',
getName: function() {
return this.name;
}
};
module.exports = lib;
这里用了fs.watch,作为监听文件改动来说可能不是那么完美,但是我们这里用来演示改动更新已经足够了。先执行entry,然后可以不停的改动lib.js中的name值看看,就会发现console出来的值已经不一样了。
总结
上面的代码只是简单的演示,虽然在生产环境中热更新一个模块文件好像并没有什么太大的意义,而在开发环境中,其实我们最常用的还是诸如nodemon这类的工具,不过,这也提供了一个思路,可以在不重新启动进程的情况下,去重新加载一到个模块。