Webpack 實(shí)現(xiàn) Node.js 代碼熱替換
這兩天為了這個問題, Gitter 上問, Twitter 上問, GitHub 上問, 兩天沒反應(yīng)
原來寫博客的 jlongster 不理我, 我也不知道 Webpack 作者的聯(lián)系方式
最后在 Gitter 上發(fā)的消息他似乎看到了, 就粗略地解釋了一遍, 醍醐灌頂啊...
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 怎么配置和腳本怎么運(yùn)行
我寫了一遍, 代碼僅僅是這么短, 熱替換就實(shí)現(xiàn)了:
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' # <-- 輪詢更新內(nèi)容的代碼 './src/main' # <-- 項目入口 ] target: 'node' # <-- 指明編譯方式為 node output: path: 'build/' filename: 'bundle.js' # <-- 編譯結(jié)果的文件名 module: loaders: [ {test: /\.coffee/, loader: 'coffee'} ] plugins: [ new webpack.HotModuleReplacementPlugin() # <-- 照常啟動 hot mode ] resolve: extensions: ['.js', '', '.coffee']
命令行環(huán)境運(yùn)行的話, 注意是 webpack 而不是 webpack-dev-server
注意后臺運(yùn)行的 & 只是為了不阻塞, 你有兩個終端就開兩個吧
npm i webpack --watch & # <-- watch 模式 node build/bundle.js # <-- 運(yùn)行的是打包結(jié)果的代碼
我寫了兩個測試文件, 一個是會修改的代碼 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 文件記錄修改的內(nèi)容:
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
具體的文件內(nèi)容也就是這樣, 大致可以認(rèn)為包含了識別更新所需的信息:
➤➤ 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(); }); } /***/ } };
其他方案
白天在網(wǎng)上查找方案, 順便在論壇上發(fā)了個帖子問這個事情,現(xiàn)成的主要兩個說明比較清楚的方案, 值得借鑒一下
一個是百度的技術(shù)博客上, 寫的大概是怎么對 module 對象做處理,也就是手工監(jiān)聽文件修改, 然后清楚模塊緩存, 重新掛載模塊
思路清晰考慮細(xì)致, 雖然有點(diǎn)冗余代碼, 還是可以一試:
http://www.dbjr.com.cn/article/73739.htm
另一個似乎是對 require.extensions 做了 hack, 增加了操作和事件,當(dāng)模塊文件更新時, 對應(yīng)模塊自動更新, 并且 emit 一個事件,通過這樣的效果, 模塊引用的位置可以做一些處理, 使用新的代碼,這個應(yīng)該說還是比較粗暴的, 畢竟不是所有的代碼都容易替換
https://github.com/rlidwka/node-hotswap
感想
考慮到我已經(jīng)在 Webpack 這棵樹上吊死, 也就不打算深入研究了,也許 Node.js 官方對 lib/module.js 做下優(yōu)化能搞出不錯的功能來,然而, JavaScript 畢竟不是不可變數(shù)據(jù)使用成風(fēng)的社區(qū), 比不了 Erlang,因?yàn)榇a替換就涉及到狀態(tài)更新的問題, 不好搞, 不如重啟來得省事,而重啟現(xiàn)在有 node-dev supervisor nodemon 三套方案任你選
對我來說, 主要是 Cumulo 方案對 WebSocket 存在巨大的依賴,現(xiàn)在前端開發(fā)已經(jīng)能做到服務(wù)器上更新代碼, 客戶端自動更新了,
通過 Webpack 和 React 的機(jī)制, 局部更新 DOM 和純函數(shù)模塊,如果說能夠做到開發(fā)環(huán)境也能熱替換, 這對于開發(fā)效率的提升就太大了,本來覺得熱替換遙不可及的, 然而很可能是觸手可及的效率提升!
后面大概還有坑, 畢竟黑科技... 遇到再說了
有興趣可以細(xì)看下 jlongster 寫的相關(guān)的幾篇神作, 非常有幫助:
http://jlongster.com/archive
相關(guān)文章
node實(shí)現(xiàn)基于token的身份驗(yàn)證
這篇文章主要介紹了node實(shí)現(xiàn)基于token的身份驗(yàn)證,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04nodejs微信開發(fā)之授權(quán)登錄+獲取用戶信息
這篇文章主要介紹了nodejs微信開發(fā)之授權(quán)登錄+獲取用戶信息,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-03-03基于node.js實(shí)現(xiàn)微信支付退款功能
在微信開發(fā)中有有付款就會有退款,這樣的功能非常常見,這篇文章主要介紹了node.js實(shí)現(xiàn)微信支付退款功能,需要的朋友可以參考下2017-12-12關(guān)于Sequelize連接查詢時inlude中model和association的區(qū)別詳解
這篇文章主要介紹了關(guān)于Sequelize連接查詢時inlude中model與association的區(qū)別,文中介紹的很詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-02-02初識NodeJS服務(wù)端開發(fā)入門(Express+MySQL)
本篇文章主要介紹了初識NodeJS服務(wù)端開發(fā)入門(Express+MySQL),可以對數(shù)據(jù)庫中的一張表進(jìn)行簡單的CRUD操作,有興趣的可以了解一下。2017-04-04NestJS核心概念之Middleware中間件創(chuàng)建使用示例
這篇文章主要為大家介紹了NestJS核心概念之Middleware中間件創(chuàng)建使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08socket.io學(xué)習(xí)教程之深入學(xué)習(xí)篇(三)
這篇文章更加深入的給大家介紹了socket.io的相關(guān)資料,之前已經(jīng)介紹了socket.io的基本教程和應(yīng)用,本文更為深入的來介紹下socket.io的使用,需要的朋友可以參考借鑒,下面來一起看看吧。2017-04-04