摘要:如果你的項目中使用了的話,你會很幸運,借助插件可以實現項目的熱更新。對模板更新的處理目前項目中使用的是的模板引擎。
這個是組內一位同學在平時開發中,發現調試不便,為團隊開發的熱更新工具。很厲害,文章中的技術實現內容也是我了解了他的具體實現思路后,整理出來的。
工具源碼EHU(esl-hot-update)
熱更新是什么熱更新就是當你在開發環境修改代碼后,不用刷新整個頁面即可看到修改后的效果。
如果你的項目中使用了webpack的話,你會很幸運,借助webpack-dev-server插件可以實現項目的熱更新。
解決的問題對于大型的系統級別項目會有下面幾個特點
模塊化(AMD)模式的廣泛使用后,開發環境散文件特別多,很容易上百,一不小心還能上千
初始化的內容特別多,各種底層庫,ui庫等等
這兩個特點直接導致每次調試后,刷新會很慢。如果初始化的js達到上千的數量級,每一次重新刷新都是5s,10s,甚至20s的等待。
而熱更新的目的就是為了在一定程度上減少這5s,10s,甚20s的浪費。
遇到的問題我們使用的是百度自己的開發環境工具edp,首先他不支持熱更新
我們使用的AMD實踐也是百度自己的esl,而且即使是requirejs也暫時沒有找到對應的熱更新策略,假如requirejs有對應的,我們也無法直接使用
所以最終的結論是我們自己去實現一個基于我們自己業務的。這樣我們考慮的面不用太廣,并且解決方案的更有針對性,即面向我們現有的業務框架。最重要的是可以嘗試修改底層框架做配合。
等待路踩通了,我們再去考慮普適性。
解決的思路從ehu/package.json 這個文件中,我們就可以看出一些具體的思路
需要一個watch功能,即能夠監聽到文件的修改
socket.io通知瀏覽器處理文件的改變
修改esl這個文件,達到能夠實時更新的效果
當時最簡單的考慮,就是文件改變了后,能夠通知瀏覽器,瀏覽器去重新load這個文件并且執行一次。這個時候再重新去打開這個模塊或者功能后,會發現新load的代碼在執行后會覆蓋上一次的。
所以當時的我的第一直覺是,esl重復require時,如果后面一次會覆蓋前面的,那么可以通過簡單的覆蓋思路去嘗試,結果發現覆蓋不了。經過驗證,發現是esl內部維護了一個map,即require過的模塊會存起來。我們如果希望更新這個模塊,只能將map中的對應模塊名刪除。(后面會詳細講述esl的改造)
對于工具的要求對應這個工具,我當時也提出了幾個要求
esl必然是需要修改的,但是如何對開發人員透明?首先是不能讓大家都做這種修改。
頁面中也必須加入socket.io支持,那么我們如何在不影響其他人員開發的情況下加入?
我們做的屬于beta版本,如何選擇性的使用?ehu工具和以前的開發模式隨意切換?
安裝方便,能否只是作為一個工具,即插即用,不需要繁瑣的配置?
如何使用npm install -g ehu(mac下需要sudo,windows下需要管理員權限)
在原來執行edp webserver start命令的路徑 執行 ehu(不再需要執行 edp webserver start)
原來端口號8848修改為8844(原8848依舊可以使用,但不支持熱更新)
首先使用的方式很簡單,為此特意將工具打包到npm上,以后就算有升級,僅僅需要大家update即可。
另外從使用角度,也盡量集成化(一句命令行即可),避免為了這個工具的使用而做太多額外的事情。
依賴的框架"dependencies": { "async": "^1.5.0", "commander": "^2.9.0", "express": "^4.13.3", "express-http-proxy": "^0.6.0", "lodash": "^3.10.1", "socket.io": "^1.3.7", "watch": "^0.16.0" }
幾個必要的
watch——監聽文件變化
socket.io——和瀏覽器的實時通訊
express——搭建一個服務
express-http-proxy——代理
commander——便于自己寫node命令
工具類:
async 和 lodash
先看看昨天對于這個工具提出的幾個要求
esl必然是需要修改的,但是如何對開發人員透明?首先是不能讓大家都做這種修改。
頁面中也必須加入socket.io支持,那么我們如何在不影響其他人員開發的情況下加入?
我們做的屬于beta版本,如何選擇性的使用?ehu工具和以前的開發模式隨意切換?
安裝方便,能否只是作為一個工具,即插即用,不需要繁瑣的配置?
對于1和2,我們其實是需要修改/添加一些代碼的,但是代碼都不希望提交到項目的開發環境,因為這些代碼生成環境完全不需要。
所以我們的解決方案是:攔截,改寫(偷梁換柱)
舉個例子,當我們需要對esl做一些改造時,我們處理方式是當路由指向esl.js時,我們換成另外一個esl-ehu.js(esl-ehu.js是對esl.js改造后的)返回去,這樣就對開發環境的代碼透明了。
socket.io的支持也是同理,我們可以在返回html時,改寫html的代碼,加入對于socket.io的引入。
上面的思路其實來源于之前項目構建打包。
對于3,我們希望在使用工具時,任然能很快切換到以前模式,這樣做兼容的目的是希望工具更有競爭力,能吸引大家使用。
我們的解決方案是:內部實現一個子線程,端口號依然是以前的,而且訪問這個端口,就繞過了這個工具。
對于子線程child_process,我們還遇到一個問題,就是子線程跑系統的時候,經常掛掉,今天剛剛找到一個解決方案,后面會單開一個文章講這個坑。
對于4,其實就是使用npm方式
技術細節第一步:搭建一個新服務作為底層,去托管住我們現在edp服務,新服務上有一個路由配置,對于我們需要處理的,攔截。對于不用處理的直接代理給edp
代碼參考
var mid = express(); mid.all("*", httpProxy(config.defaultServer, { // 先走特殊規則,否則就代理到默認web server filter: function(req, res) { return !ruleRoute(req, res); }, forwardPath: function(req, res) { return URL.parse(req.url).path; } })); // 由express-http-proxy托管路由 app.use("/", mid);
ruleRoute就是一些攔截處理
在此之前,啟動下子進程
var child = require("child_process"); var cli = child.exec(defaultServerCLI); cli.stdout.on("data", function (log) { !isServerStarted && (cb(null, log)); isServerStarted && console.log(log); });
此處有坑,后面單開文章描述
第二步: 因為上面攔截后的返回的文件已經支持socket.io,esl等底層已經修改了,所以下面是需要去監聽文件通知瀏覽器做對應處理。
// 啟動socket.io服務 io = require("socket.io")(server); io.on("connection", function (socket) { socket.emit("hello"); }); // 監視文件改動 initWatch();
第三步: 做一些集成工作
program .version("0.0.6") .usage("[options]") .option("-p, --port", "Set the port", setPort) .option("-n, --noServerCLI", "...", noServerCLI) .parse(process.argv);
集成到node命令中
第四步: 默認配置
module.exports = { // 默認的服務器 defaultServer: "http://127.0.0.1:8848", // 默認的服務器啟動命令 defaultServerCLI: "edp webserver start", // 從服務器根目錄到需要監控的文件夾中間path baseDir: "nirvana-workspace", // hot update 需要watch的文件夾(不包括baseDir) watchDirs: "src", // 入口文件(不包括baseDir) indexHTML: "main.html", // ehu啟動端口號(不可與默認的服務器端口號沖突) port: 8844 };
源碼中有很多邏輯是處理配置的
瀏覽器依賴socket.io——瀏覽器端僅僅依賴socket這個去和服務端通信
通信邏輯
// 建立連接 socket.on("hello", function () { log(getLogMsgPrefix(), "HotUpdate已啟動!"); }); // 檢測到文件改動 socket.on("hotUpdate", function (file) { // log(getLogMsgPrefix(), "檢測到文件改動", file); // ....處理文件修改后對應熱更新邏輯 });對css/less更新的處理
這個原理比較簡單,頁面監聽到樣式的修改,重新加載一次樣式即可,簡單的覆蓋。
但是存在一個潛在問題,因為樣式是簡單的覆蓋,所以,如果修改是刪除了樣式,是無法生效的。
舉例:
修改前:
display: none; overflow: hidden; position: relative; background: #FFFFFF; border: 1px solid #E8E8E8; margin-top: 20px;
修改后:
display: none; overflow: hidden; position: relative; background: #FFFFFF;
刪除的border和margin-top其實是沒有生效的
這個也是后期需要解決的一個問題。
對模板更新的處理目前項目中使用的是tpl的模板引擎。
現在就遇到一個問題,在熱更新時,模板引擎其實是重復加載模板的,那么就涉及到重復加載是否后面的會覆蓋前面問題。
查看加載模板的源碼后,發現根據配置有三個選擇,覆蓋,忽略和報錯, 我們業務中使用的配置是遇到重復后會報錯處理,所以我們需要在不修改業務默認屬性的情況下,添加一些邏輯。
// [esl-hot-update] 重新加載需要覆蓋 window.EHU_HOT_UPDATE_OPTIONS && window.EHU_HOT_UPDATE_OPTIONS.etpl.isOverride && (namingConflict = "override"); switch (namingConflict) { /* jshint ignore:start */ case "override": engine.targets[name] = target; context.targets.push(name); case "ignore": break; /* jshint ignore:end */ default: throw new Error("Target exists: " + name); }
window.EHU_HOT_UPDATE_OPTIONS.etpl.isOverride這個是修改后自己實現的控制配置修改的邏輯。
然后這個文件加入到服務端的路由中,請求時替換。
對js更新的處理這里邏輯比較復雜,因為需要修改底層的AMD模塊加載的邏輯。
js沒有模板那么簡單,不是直接覆蓋,因為在AMD模式中,每一個文件,都是被上一個文件調用執行的結果。
所以我們處理的邏輯是不僅需要重新加載修改的文件,并且遞歸所有直接或者間接調用他的文件,全部重新加載。
所以從上面的特點可以看出,這個工具目前階段主要適用于業務模塊的開發,因為業務的依賴不會特別深,對于dep中的核心文件修改,就不是很合適,一旦文件比較底層,熱跟新是重新加載的模塊也會非常多。
另外也有很多其他的坑,還在不斷優化中。
總結這次實踐其實就是業務中遇到的問題(系統太龐大,調試太麻煩),如何解決問題,如何把解決的思路變成一個解決方案,分享給團隊。
因為自己解決了,和形成一個解決方案還是有非常大的差別的,例如我們在形成方案的過程中,就嘗試了很多新東西,踩了很多坑。
目前還有個坑就是chrome瀏覽器,調試的Source資源時,如果一個資源重復加載,內存中會更新,但是對應的資源沒有更新,導致斷點時,映射不對(斷點失效),目前暫時的解決方案是,每次請求時添加時間戳,讓Source映射的資源強制更新。這個可以正常斷點,但是斷點沒有記憶功能(坑啊,因為文件變了)。
微信公眾號 博客地址http://tangguangyao.github.io/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78318.html
摘要:最后發現使用子進程打開還真的就是使用到一定程度就掛掉。上面的簡單流程就是啟動一個子進程。邏輯就是,記錄子進程的大小,一旦超過就掉子進程。我們在使用時,不知道設置,默認的是當我們子進程日志達到時,自動掉了。 showImg(https://segmentfault.com/img/bVrCnh); 如何在項目中實現熱更新中提到的一個坑child_process的exec使用問題,下面文章...
摘要:注本文章是在工作過程中所接觸的知識點的整理,涉及的東西比價雜亂,如有錯誤之處,歡迎糾錯與指導一,頁面的錨鏈接定義錨點錨點鏈接。類似于我們閱讀書籍時的目錄頁碼或章回提示。 *注:本文章是在工作過程中所接觸的知識點的整理,涉及的東西比價雜亂,如有錯誤之處,歡迎糾錯與指導 一, 頁面的錨鏈接 1,定義:錨點,錨點鏈接。常常用于那些內容龐大繁瑣的網頁,通過點擊命名錨點,不僅讓我們能指向文檔,還...
摘要:注本文章是在工作過程中所接觸的知識點的整理,涉及的東西比價雜亂,如有錯誤之處,歡迎糾錯與指導一,頁面的錨鏈接定義錨點錨點鏈接。類似于我們閱讀書籍時的目錄頁碼或章回提示。 *注:本文章是在工作過程中所接觸的知識點的整理,涉及的東西比價雜亂,如有錯誤之處,歡迎糾錯與指導 一, 頁面的錨鏈接 1,定義:錨點,錨點鏈接。常常用于那些內容龐大繁瑣的網頁,通過點擊命名錨點,不僅讓我們能指向文檔,還...
摘要:已經發布到,只要在環境下安裝即可。下面通過來構建開發環境,提高開發體驗。容器容器的實質是進程,但與直接在宿主執行的進程不同,容器進程運行于屬于自己的獨立的命名空間。部署開發環境部署開發環境其實很簡單,只需要配置和即可。 前言 本次博文依然是對 multi-spa-webpack-cli 的擴充和完善。 集成 mongoose。 集成 Docker 開發環境。 multi-spa-w...
摘要:通過使用有限的類標簽閹割的及基于語法來快速構建原生應用。高性能本身對加載時間和資源占用進行了優化。站在巨人的肩膀上,我們也很容易開發出高性能的。我們可以把部署到服務器上實現熱更新。引擎運行這些實現與線程通信,達到和原生應用相同的體驗效果。 和一步一起從前端視角聊一聊WEEX WEEX是一套構建高性能、可擴展的原生應用跨平臺解決方案。就一個字吊。 通過使用有限的類HTML標簽、閹割的CS...
閱讀 2473·2021-11-24 09:39
閱讀 3405·2021-11-15 11:37
閱讀 2250·2021-10-08 10:04
閱讀 3965·2021-09-09 11:54
閱讀 1882·2021-08-18 10:24
閱讀 1032·2019-08-30 11:02
閱讀 1792·2019-08-29 18:45
閱讀 1651·2019-08-29 16:33