摘要:直到最近在使用微信機(jī)器人時(shí),遇到了強(qiáng)烈的需求。增刪文件后熱更新上面的代碼已經(jīng)不小心實(shí)現(xiàn)了增加文件后熱更新,因?yàn)楸硎緳z測(cè)的更新,如果增加一個(gè),那么就變成,于是新模塊不等于老模塊不存在,從而使用注冊(cè)事件監(jiān)聽器。
背景
剛思考這個(gè)話題的時(shí)候,首先想到的是 Vue 或 React 的組件熱更新(基于 Webpack HMR),后來又想到了 Lua、Erlang 等語言的熱更新,不過在實(shí)際開發(fā) Node.js 后臺(tái)時(shí),使用 remy/nodemon 之類的熱重啟(偵測(cè)代碼改動(dòng)重啟程序)工具也夠用,于是 Node.js 的熱更新(替換模塊,無須重啟)的驗(yàn)證就一直擱置。
直到最近在使用「微信機(jī)器人」)(Node.js) 時(shí),遇到了強(qiáng)烈的需求。這類機(jī)器人程序就是:啟動(dòng)了一個(gè)網(wǎng)頁,登錄 Web 微信,通過抓取識(shí)別頁面中的元素獲得一些狀態(tài)信息,如:消息、好友請(qǐng)求等等,由于它的啟動(dòng)時(shí)間也比較長,如果每次修改業(yè)務(wù)代碼后都要重啟,那么等待程序啟動(dòng)就要消耗不少時(shí)間,導(dǎo)致開發(fā)體驗(yàn)很差,于是實(shí)踐 Node.js 的熱更新就迫在眉睫了。
目標(biāo)以下是機(jī)器人的核心用法:
robot = new Robot() robot.addEventListener("msg", ...) robot.removeEventListener("msg", ...)
那么我們的目標(biāo):增/刪/改 業(yè)務(wù)邏輯(事件處理器)的時(shí)候程序無須重啟,自動(dòng)熱更新業(yè)務(wù)邏輯代碼,從而提高開發(fā)效率。
思路一:基于 Webpack 驗(yàn)證可行從 Webpack Wiki hot module replacement · webpack/docs Wiki 了解到,Webpack 能知道「哪個(gè)模塊需要熱更新」,并提供一些鉤子,另外 webpack 自有一套模塊管理,能夠管理替換模塊,讓你訪問的是熱更新之后的模塊。另外,要實(shí)現(xiàn)熱加載的不僅要滿足「再次加載」,還要考慮如何清空相關(guān)的「持久資源」。
所以說,如果基于 webpack HMR 來實(shí)現(xiàn)的話,需要完成幾件事情:
把事件處理器的代碼模塊化,便于 webpack 管理。
自動(dòng)加載所有處理器模塊
某個(gè)事件處理模塊更新后需要拿到老的模塊,用來移除老的監(jiān)聽處理器。
要知道文件的增加和刪除,并且拿到模塊內(nèi)容。
1. 業(yè)務(wù)代碼模塊化簡單地把每個(gè)事件處理器定義為一個(gè)文件 *.biz.js:
// msg.biz.js module.exports = { evt: "msg", fn() { console.log("msg hanlder....") } };
其中 evt 是事件名, fn 是處理器,于是加載一個(gè)業(yè)務(wù)模塊后就能拿到事件名稱和處理器。
(可能不滿足實(shí)際要求,先簡單驗(yàn)證熱更新是否可行哈!)
我們約定,業(yè)務(wù)模塊 *.biz.js 都放在 /biz 目錄下,該目錄下的 index.js 會(huì)加載所有業(yè)務(wù)模塊,而 main.js 就只需加載 /biz/index.js
src |--- /biz |--- a.biz.js |--- b.biz.js |--- index.js |--- main.js
借助 webpack 的 require-context 加載所有 *.biz.js 模塊,避免手寫 require:
// index.js // 加載當(dāng)前目錄下所有 `*.biz.js` const requireContext = require.context("./", true, /.biz.js/); // 此時(shí) requireContext.keys() 為 ["./a.biz.js", "./b.biz.js"] requireContext.keys().forEach(key => { const module = requireContext(key); // 相當(dāng)于 module = require("./biz/a.biz.js") // 于是拿到事件名和處理器,然后進(jìn)行事件監(jiān)聽 // robot.addEventListener(module.evt, module.fn) });3. 修改后熱更新
參考 Wiki 的例子 Example 3,知道 require.context 如何使用熱更新機(jī)制
// index.js // 啟動(dòng) webpack HRM 時(shí)則 module.hot 為 true if (module.hot) { // 表示該 context 下的模塊都要檢測(cè)更新 module.hot.accept(requireContext.id, () => { const requireContext = require.context("./", true, /.biz.js/); requireContext.keys().forEach(key => { const newModule = requireContext(key); // 前面首次自動(dòng)加載所有模塊后,記錄到 oldModules 對(duì)象() // 如果模塊內(nèi)容不一樣,則表示要作熱更新處理了 if (oldModules[key] !== newModule) { // ... 對(duì)老模塊 oldModules[key] 移除事件監(jiān)聽 // ... 對(duì)新模塊 newModule 注冊(cè)事件監(jiān)聽 // 同時(shí)更新緩存記錄 oldModules[key] = newModule; } }); }); }
到了這一步,修改任何 *.biz.js 的代碼都能自動(dòng)熱更新了。
4. 增刪文件后熱更新上面的代碼已經(jīng)不小心實(shí)現(xiàn)了 「增加文件后熱更新」,因?yàn)?module.hot.accept(requireContext.id 表示檢測(cè) ./biz/*.biz.js 的更新,如果增加一個(gè) c.biz.js,那么 requireContext.keys() 就變成 [ ..., "./c.biz.js"],于是新模塊不等于老模塊(不存在),從而使用 c.biz.js 注冊(cè)事件監(jiān)聽器。
對(duì)于刪除文件后的熱更新,則在上面代碼基礎(chǔ)上增加:
if (module.hot) { module.hot.accept(requireContext.id, () => { // 在重新加載目錄下的所有模塊前,對(duì)老記錄作個(gè)副本 const oldKeysRetain = {}; Object.keys(oldModules) .forEach(k => (oldKeysRetain[k] = true)); const requireContext = require.context("./", true, /.biz.js/); requireContext.keys().forEach(key => { // 如果某模塊存在當(dāng)前目錄,則從臨時(shí)記錄中抹去 delete oldKeysRetain[key]; const newModule = requireContext(key); if (oldModules[key] !== newModule) { ... } }); // 未抹去的部分,意味著不存在當(dāng)前目錄下了,也就是被刪除了 Object.keys(oldKeysRetain).forEach(key => { // ... 對(duì)老模塊移除事件監(jiān)聽 delete oldModules[key]; }); }); }
經(jīng)過以上四步,算是初步驗(yàn)證了,借助 Webpack 來玩是可以的,當(dāng)然我們作了不少嚴(yán)格約定,不過不影響這一階段的思路。
完整代碼請(qǐng)移步:zhenyong/webpack-hot-nodejs-demo: Webpack HMR demo use in Node.js, showing how to auto add/remove listeners.
思路二:基于 Webpack 進(jìn)階上面一種思路存在一些問題
業(yè)務(wù)代碼的格式限制太死,不夠靈活
在生產(chǎn)階段也耦合了 webpack
于是我想,約定業(yè)務(wù)代碼格式是為了方便通過模塊管理事件的注冊(cè)和移除,假如說在不侵入代碼,不作任何約定的情況下,也能知道某個(gè)模塊注冊(cè)了哪些事件,是不是就不需約定了,好像是的:
//## a.biz.js 不約定業(yè)務(wù)代碼格式 robot.addLisenter("msg", ...) //## 入口.js robot = new Robot(); _add = robot.addLisenter robot.addLisenter = () => { // 攔截注冊(cè)事件方法 // 從而記錄下 a.biz 模塊都注冊(cè)了哪些事件處理器 } require("a.biz") robot.addLisenter = _add
但是問題來了,我們的目標(biāo)包括「自動(dòng)加載所有業(yè)務(wù)模塊,增刪文件都能熱更新」,那么在開發(fā)階段我們還是借助 webpack 的 require.context 方法,并且約定每個(gè)業(yè)務(wù)模塊的入口文件命名為 *.biz.js,至于里面代碼怎么寫就隨意了,而在生產(chǎn)階段可以遍歷文件找到所有 *.biz.js 進(jìn)行加載,無須依賴 webpack。
剩下的大部分思路跟 #思路一 類似,代碼可參考 zhenyong/webpack-hot-nodejs-demo: Webpack HMR demo use in Node.js, showing how to auto add/remove listeners.
更多思路最開始寫這篇文章是想深扒一下 Node.js 的模塊管理和緩存結(jié)構(gòu),然后驗(yàn)證一下通過清除模塊緩存來做熱更新是否可行,后來感覺 webpack 給我們作了很多工作,于是就先用 webpack 玩了一輪,看來擇日還得再寫一篇(二)了
問題熱更新的主要目的是為了提高開發(fā)效率,并不是為了在生產(chǎn)上玩熱更新,畢竟還有很多潛在問題,例如,模塊中涉及全局狀態(tài)或者單例資源,通過熱更新可能會(huì)引起混亂......
參考Webpack 做 Node.js 代碼熱替換, 第一步 - 題葉 - SegmentFault
Backend Apps with Webpack (Part I)
Backend Apps with Webpack: Driving with Gulp (Part II)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/82416.html
摘要:從事開發(fā)的程序員,對(duì)于前后端分離模式多半不陌生,這也是目前主流的開發(fā)模式,具體關(guān)于前后端分離的模式可以參看文章你不得不了解的前后端分離原理,在這里寫者不進(jìn)行說明。原理圖如下,前后端在一個(gè)進(jìn)程同一個(gè)端口中,通過熱替換更新的,而不是全量重啟。 從事 Web 開發(fā)的程序員,對(duì)于前后端分離模式多半不陌生,這也是目前主流的 Web 開發(fā)模式,具體關(guān)于前后端分離的模式可以參看文章《你不得不了解的前...
摘要:在前后端分離的前端項(xiàng)目開發(fā)中經(jīng)常用到。是的一個(gè)中間件。即是一個(gè)重要的功能。配置先來在配置文件中引入添加一個(gè)和通信的客戶端添加應(yīng)用入口文件在插件中引入在我們的開發(fā)環(huán)境中是這樣配置的。 原文鏈接此文是我同事寫的,搭建Express結(jié)合Webpack。以下是正文,后面我會(huì)附上我的解讀 Express 結(jié)合 Webpack 實(shí)現(xiàn)HMR 本篇文件主要講結(jié)合 Webpack 和 Express 實(shí)...
摘要:在前后端分離的前端項(xiàng)目開發(fā)中經(jīng)常用到。是的一個(gè)中間件。即是一個(gè)重要的功能。配置先來在配置文件中引入添加一個(gè)和通信的客戶端添加應(yīng)用入口文件在插件中引入在我們的開發(fā)環(huán)境中是這樣配置的。 原文鏈接此文是我同事寫的,搭建Express結(jié)合Webpack。以下是正文,后面我會(huì)附上我的解讀 Express 結(jié)合 Webpack 實(shí)現(xiàn)HMR 本篇文件主要講結(jié)合 Webpack 和 Express 實(shí)...
摘要:使用要給項(xiàng)目構(gòu)建接入動(dòng)態(tài)鏈接庫的思想,需要完成以下事情把網(wǎng)頁依賴的基礎(chǔ)模塊抽離出來,打包到一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫中去。接入已經(jīng)內(nèi)置了對(duì)動(dòng)態(tài)鏈接庫的支持,需要通過個(gè)內(nèi)置的插件接入,它們分別是插件用于打包出一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫文件。 webpack優(yōu)化 查看所有文檔頁面:全棧開發(fā),獲取更多信息。原文鏈接:webpack優(yōu)化,原文廣告模態(tài)框遮擋,閱讀體驗(yàn)不好,所以整理成本文,方便查找。 ...
摘要:馬上要出了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節(jié)詳細(xì)討論這個(gè)過程。的事件流機(jī)制保證了插件的有序性,使得整個(gè)系統(tǒng)擴(kuò)展性很好。 webpack馬上要出5了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。 本文書寫時(shí)間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗(yàn)過可...
閱讀 2609·2021-11-22 15:25
閱讀 1429·2021-11-15 17:59
閱讀 1129·2021-09-29 09:34
閱讀 1535·2021-09-26 09:46
閱讀 3030·2021-09-02 15:40
閱讀 1190·2019-08-30 15:56
閱讀 3282·2019-08-30 15:55
閱讀 693·2019-08-29 17:08