Webpack 实现 Node.js 代码热替换


Posted in Javascript onOctober 22, 2015

这两天为了这个问题, Gitter 上问, Twitter 上问, GitHub 上问, 两天没反应
原来写博客的 jlongster 不理我, 我也不知道 Webpack 作者的联系方式
最后在 Gitter 上发的消息他似乎看到了, 就粗略地解释了一遍, 醍醐灌顶啊...
https://github.com/webpack/docs/issues/45#issuecomment-149793458

Here is the process in short:

Compile the server code with webpack
Use target: "node" or target: "async-node"
Enabled HMR via --hot or HotModuleReplacementPlugin
Use webpack/hot/poll or webpack/hot/signal
The first polls the fs for updates (easy to use)
The second listens for a process event to check for updates (you need a way to send the signal)
Run the bundle with node.
You can't use existing HMR loaders like react-hot-loader or style-loader because they make no sense in a server environment. Just add manuall replacement code at the correct location (i. e. accept request handler like in the example)

You can't use the webpack-dev-server. It's a server which serves assets not a runner. Just run webpack --watch and node bundle.js. I would go the webpack/hot/poll?1000 route first. It's pretty easy to use and suitable for dev environments. For production (if you want to hot update your production server) the signal approach is better suited.

原话就不翻译了, 理解之后主要就是 Webpack 怎么配置和脚本怎么运行
我写了一遍, 代码仅仅是这么短, 热替换就实现了:
https://github.com/jiyinyiyong/webpack-backend-HMR-demo
其中代码可以从 jlongster 的配置教程里抄:
http://jlongster.com/Backend-Apps-with-Webpack--Part-II

webpack = require 'webpack'

module.exports =
 entry: [
  'webpack/hot/poll?1000' # <-- 轮询更新内容的代码
  './src/main' # <-- 项目入口
 ]
 target: 'node' # <-- 指明编译方式为 node
 output:
  path: 'build/'
  filename: 'bundle.js' # <-- 编译结果的文件名
 module:
  loaders: [
   {test: /\.coffee/, loader: 'coffee'}
  ]
 plugins: [
  new webpack.HotModuleReplacementPlugin() # <-- 照常启动 hot mode
 ]
 resolve:
  extensions: ['.js', '', '.coffee']

命令行环境运行的话, 注意是 webpack 而不是 webpack-dev-server
注意后台运行的 & 只是为了不阻塞, 你有两个终端就开两个吧

npm i
webpack --watch & # <-- watch 模式
node build/bundle.js # <-- 运行的是打包结果的代码

我写了两个测试文件, 一个是会修改的代码 src/lib.coffee:

exports.data = 'code 5'

exports.printSelf = ->
 console.log 'doing 3'

另一个入口文件 src/main.coffee 包含了处理模块替换的代码:

lib = require './lib'

console.log lib.data
lib.printSelf()

counter = 0
setInterval ->
 counter += 1
 console.log counter
, 2000

if module.hot
 module.hot.accept './lib', ->
  lib = require './lib'

  console.log lib.data
  lib.printSelf()

跑一跑 Demo, 就知道效果怎么样了, setInterval 不受替换的干扰
而在 build/ 目录, 每次修改都会生成一个 JSON 文件记录修改的内容:

➤➤ l build/

0.1dadeb2eb7b01e150126.hot-update.js  0.c1d0d73de39660806d0c.hot-update.js  2849b61a15d31ffe5e08.hot-update.json  0.99ea3ea7633f6b3750e6.hot-update.js  0.eaa7b323eba37ae58997.hot-update.js  9b4a5ad617ec1dbc48a3.hot-update.json  fb584971920454f9ccbe.hot-update.json

0.9abf25005c61357a0ce5.hot-update.js  0.fb584971920454f9ccbe.hot-update.js  a664b5851a99ac0865ca.hot-update.json

0.9b4a5ad617ec1dbc48a3.hot-update.js  1dadeb2eb7b01e150126.hot-update.json  bundle.js

0.a664b5851a99ac0865ca.hot-update.js  256267122c6d325755b0.hot-update.json  c1d0d73de39660806d0c.hot-update.json

具体的文件内容也就是这样, 大致可以认为包含了识别更新所需的信息:

➤➤ cat build/0.c797c084381bfeac37f7.hot-update.js
exports.id = 0;
exports.modules = {

/***/ 3:
/***/ function(module, exports, __webpack_require__) {

  var counter, lib;
  lib = __webpack_require__(4);
  console.log(lib.data);
  lib.printSelf();
  counter = 0;
  setInterval(function() {
   counter += 1;
   return console.log(counter, 3);
  }, 2000);

  if (true) {
   module.hot.accept(4, function() {
    lib = __webpack_require__(4);
    console.log(lib.data);
    return lib.printSelf();
   });
  }

/***/ }
};

其他方案

白天在网上查找方案, 顺便在论坛上发了个帖子问这个事情,现成的主要两个说明比较清楚的方案, 值得借鉴一下

一个是百度的技术博客上, 写的大概是怎么对 module 对象做处理,也就是手工监听文件修改, 然后清楚模块缓存, 重新挂载模块
思路清晰考虑细致, 虽然有点冗余代码, 还是可以一试:
https://3water.com/article/73739.htm

另一个似乎是对 require.extensions 做了 hack, 增加了操作和事件,当模块文件更新时, 对应模块自动更新, 并且 emit 一个事件,通过这样的效果, 模块引用的位置可以做一些处理, 使用新的代码,这个应该说还是比较粗暴的, 毕竟不是所有的代码都容易替换
https://github.com/rlidwka/node-hotswap

感想

考虑到我已经在 Webpack 这棵树上吊死, 也就不打算深入研究了,也许 Node.js 官方对 lib/module.js 做下优化能搞出不错的功能来,然而, JavaScript 毕竟不是不可变数据使用成风的社区, 比不了 Erlang,因为代码替换就涉及到状态更新的问题, 不好搞, 不如重启来得省事,而重启现在有 node-dev supervisor nodemon 三套方案任你选

对我来说, 主要是 Cumulo 方案对 WebSocket 存在巨大的依赖,现在前端开发已经能做到服务器上更新代码, 客户端自动更新了,
通过 Webpack 和 React 的机制, 局部更新 DOM 和纯函数模块,如果说能够做到开发环境也能热替换, 这对于开发效率的提升就太大了,本来觉得热替换遥不可及的, 然而很可能是触手可及的效率提升!

后面大概还有坑, 毕竟黑科技... 遇到再说了

有兴趣可以细看下 jlongster 写的相关的几篇神作, 非常有帮助:
http://jlongster.com/archive

Javascript 相关文章推荐
JS时间选择器 兼容IE6,7,8,9
Jun 26 Javascript
JS去除数组重复值的五种不同方法
Sep 06 Javascript
jquery渐隐渐显的图片幻灯闪烁切换实现方法
Feb 26 Javascript
Node.js编程中客户端Session的使用详解
Jun 23 Javascript
js实现a标签超链接提交form表单的方法
Jun 24 Javascript
JQuery 设置checkbox值二次无效的解决方法
Jul 22 Javascript
完美解决jQuery 鼠标快速滑过后,会执行多次滑出的问题
Dec 08 Javascript
JS在浏览器中解析Base64编码图像
Feb 09 Javascript
微信小程序 chooseImage选择图片或者拍照
Apr 07 Javascript
Vue.js使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用
May 10 Javascript
通过cordova将vue项目打包为webapp的方法
Feb 02 Javascript
微信小程序实现滚动Tab选项卡
Nov 16 Javascript
JS+CSS实现仿msn风格选项卡效果代码
Oct 22 #Javascript
Node.js巧妙实现Web应用代码热更新
Oct 22 #Javascript
深入剖析JavaScript编程中的对象概念
Oct 21 #Javascript
JavaScript中Boolean对象的属性解析
Oct 21 #Javascript
深入解析JavaScript中的数字对象与字符串对象
Oct 21 #Javascript
jQuery无刷新切换主题皮肤实例讲解
Oct 21 #Javascript
JavaScript操作HTML元素和样式的方法详解
Oct 21 #Javascript
You might like
mysql 全文搜索 技巧
2007/04/27 PHP
PHP生成器简单实例
2015/05/13 PHP
盘点PHP和ASP.NET的10大对比!
2015/12/24 PHP
ThinkPHP中order()使用方法详解
2016/04/19 PHP
php中的登陆login实例代码
2016/06/20 PHP
如何动态加载外部Javascript文件
2015/12/02 Javascript
Bootstrap精简教程中秋大放送
2016/09/15 Javascript
表单input项使用label同时引用Bootstrap库导致input点击效果区增大问题
2016/10/11 Javascript
微信js-sdk分享功能接口常用逻辑封装示例
2016/10/13 Javascript
nodejs读写json文件的简单方法(必看)
2017/03/09 NodeJs
基于JavaScript实现五子棋游戏
2020/08/26 Javascript
JavaScript 中的12种循环遍历方法【总结】
2018/05/31 Javascript
layui form.render('select', 'test2') 更新渲染的方法
2019/09/27 Javascript
基于Echarts图表在div动态切换时不显示的解决方式
2020/07/20 Javascript
[02:16]DOTA2英雄基础教程 干扰者
2014/01/15 DOTA
[01:14:19]NAVI vs Mineski 2019国际邀请赛淘汰赛 败者组BO1 8.20.mp4
2020/07/19 DOTA
Python 文件操作实现代码
2009/10/07 Python
python编写暴力破解FTP密码小工具
2014/11/19 Python
Python常用时间操作总结【取得当前时间、时间函数、应用等】
2017/05/11 Python
Python3实现抓取javascript动态生成的html网页功能示例
2017/08/22 Python
Python实现矩阵加法和乘法的方法分析
2017/12/19 Python
Django渲染Markdown文章目录的方法示例
2019/01/02 Python
python图像处理模块Pillow的学习详解
2019/10/09 Python
python 实现目录复制的三种小结
2019/12/04 Python
pandas读取csv文件提示不存在的解决方法及原因分析
2020/04/21 Python
django下创建多个app并设置urls方法
2020/08/02 Python
解决python打开https出现certificate verify failed的问题
2020/09/03 Python
python中scipy.stats产生随机数实例讲解
2021/02/19 Python
图片上传插件ImgUploadJS:用HTML5 File API 实现截图粘贴上传、拖拽上传
2016/01/20 HTML / CSS
加拿大城市本地限时优惠:Buytopia.ca
2018/09/19 全球购物
银行介绍信范文
2014/01/10 职场文书
2014年高三毕业生自我评价
2014/01/11 职场文书
七年级政治教学反思
2014/02/03 职场文书
2014年清明节网上祭英烈寄语
2014/04/09 职场文书
2014年仓库保管员工作总结
2014/12/03 职场文书
Python查找算法的实现 (线性、二分,分块、插值查找算法)
2022/04/24 Python