node.js 的 模块加载机制和server端hot reload热加载实践

工作上遇到个后台的项目,express + vue 写的,用了Webpack Hot Middleware配合webpack-dev-middleware来让客户端连接到客户端,使用nodemon来监听重启服务器。但是webpack打包前端静态资源的速度还是比较慢的,每一次对server端的改动都要等待重新打包,但客户端的文件其实并没有改动,这么做就浪费了时间。所以,想摸索一下服务器端热加载的方法,不再用nodemon监听,在改动server端代码的时候,前端该干嘛干嘛,不要掺和进来重新打包了。

想法来源

node.js的模块加载机制是commonjs的,与es6的export/import有所区别。虽然babel可以把它转成es6的语法,但本质不变。可以通过删除require.cache来去除缓存的node module。
仿照了这个实例ultimate-hot-reloading的思路,不过例子的文件结构很简单,实际项目中模块引用可能一层套一层的,还是有些地方需要注意。

基本思路

  1. 监听文件变动(chokidar, fs.watchFile)
    使用chokidar来监听文件的变动
  2. 在变动的范围内去缓存decache ,可以采用插件如clear-require, recache 等, 也可以手动 decache )
  3. 引用的模块要以函数的形式动态使用,不能保存在变量中

坑点

1:注意父模块的引用关系,可能造成 内存泄露
详见 https://zhuanlan.zhihu.com/p/34702356
解决办法是在decache掉子模块后,也decache父模块

注意,server启动之后,是不能直接去掉app.js文件的缓存的。
2. 不再用nodemon或者supervisor监听全部文件。如果不改动app.js的话,用Node直接启动。若用import/export 代替了 require 写法,启动程序时用Babel-node 代替node,也可在配置里改babel。

        npm i —save-dev babel-cli

3: 动态引入模块,否则该模块会以变量的形式缓存在父模块中,及时去掉了该模块的缓存,因为父模块没变,等于并没有重新加载。通常做法是在app.js中动态引用路由。通过加入插件dynamic-import来完成,写成await import(‘moduleYouNeed’)的形式。

最佳实践

最佳实践应该是仍然用nodemon启动服务器,通过配置nodemon.json,只监听app.js,对其他服务器端的文件ignore,而用delete require.cache[id]这种方式在webpack-dev-middleware和webpack-hot-middleware被使用之前监听和去掉其余server端的文件。

主体代码

const watcher = chokidar.watch(path.resolve(__dirname, './routes'))
  watcher.on('ready', function() {
    console.log('will watching at ', path.resolve(__dirname, './routes'))
    watcher.on('all', function(event, file) {
      Object.keys(require.cache).forEach(function(id) {
        if(/\/www\//.test(id)) {
          console.log('before decache\n', id)
          delete require.cache[file] 
        }
      });
      logger.info('originPath', file, '\n')
    Object.keys(require.cache).forEach(function(id) {
      if(/\/www\//.test(id)) {
        console.log('after\n', id)
      }
    });
  });
});

猜你喜欢

转载自blog.csdn.net/github_36487770/article/details/81042257