本文的要點不在移動端除錯上,移動端除錯無非就是除錯頁面和除錯工具之間存在分離,消除這種分離並建立連結就能解決移動端的除錯問題。重點闡述的是所見即所得的除錯模式下會遇到的阻礙。
當我們開啟網頁,發現一個模組沒有正確地渲染或者空白時,如果控制檯有報錯,會直接根據報錯定位到原始碼位置開始 debug;如果控制檯沒有報錯,則會根據模組名或者模組特徵的一個值,通過全域性搜尋找到這個模組的位置,然後在除錯工具中斷點,單步除錯,找到問題所在,此時我們可能會這樣做:
情形一:
小A同學開啟控制檯,發現斷點除錯不好寫程式碼,於是將壓縮的原始碼複製一份儲存到本地,格式化,然後將線上資源通過代理工具代理到本地檔案。
情形二:
小B同學早早的為自己配了一份本地開發環境,於是他遇到問題之後,直接去原始碼中定位錯誤位置,由於使用的是預處理語言,所以需要先打包編譯之後再在本地預覽效果。
情形三:
小C同學的除錯方式是小A和小B的綜合版本,將線上的資源代理到本地 build 目錄檔案,在 src 目錄下修改之後編譯打包到 build,然後預覽。
☞ 代理除錯的煩惱
而對於比較複雜的線上環境,代理也會遇到很多障礙,比如:
線上資源 combo
出現錯誤的指令碼地址為
http://example.com/path/??a.js,b.js,c.js ,它對應著 a.js
,b.js
,c.js
三個指令碼檔案,如果我們使用 Fiddler/Charles 這樣的經典代理工具除錯程式碼,就必須給這些工具編寫外掛,或者在替換配置裡頭加一堆判斷或者正則,成本高,門檻高。
線上程式碼壓縮
打包壓縮,這是上線之前的必經流程。由於我們在打包的環節中並沒有考慮為程式碼新增 sourceMap,而線上之前對應 index-min.js
的 index.js
也因為安全方面的原因給幹掉了,這給我們除錯程式碼造成了極大的不便利。
程式碼依賴較多,拉取程式碼問題
很多時候,我們的頁面依賴了多個 asserts 資源,而這些資源各自分佈在多個倉庫之中,甚至分佈在不同的釋出平臺上,為了能夠在原始碼上清晰的除錯程式碼,我們不得不將所有的資源下載到本地,期間一旦存在下載程式碼的許可權問題,整個除錯進度就慢下來,這是十分不能忍受的事情。比如某系統構建的頁面,頁面上的模組都是以倉庫為維度區分的,一個頁面可能對應了5-50個倉庫,下載程式碼實為麻煩。
最可怕的除錯是,本地沒有對應的測試環境、代理工具又不滿足我們的需求,然後就只能, 編輯程式碼->打包壓縮->提交程式碼->檢視效果->編輯程式碼->... ,如果你的專案開發是這種模式,請停下來,思考除錯優化方案,正所謂磨刀不誤砍柴工。
☞ 開啟懶人除錯模式
當看到線上出現問題(可能是其他同學負責頁面的問題),腦中浮出這樣的場景:
1 2 3 4 5 6 7 8 |
複製程式碼 我:"嘿,線上有問題啦!我要除錯程式碼!" 電腦:"好的,主人。請問是哪個頁面?"(彈出浮層) 我:浮層中輸入URL。 電腦:"請問是哪個地方出問題了?" 我:(指著電腦)"模組A和模組B。" 電腦:正在下載A、B資源...正在將上線A、B對映到本地...自動開啟A、B對應資料夾 我:編輯程式碼,然後實時預覽效果。 |
在這裡我們需要解決這樣幾個問題
- 將頁面對應的所有倉庫/資源羅列在使用者面前
- 下載資源的許可權提示和許可權處理
- 線上資源解 combo,然後對映到本地
當然除錯之後,可以還有一個操作:
1 2 |
我:"哈,已經修復了,幫我提交程式碼~" 電腦:正在diff程式碼...收到確認提交訊號,提交到預發環境...收到已經預覽訊號...正在釋出程式碼...收到線上迴歸訊號...流程結束 |
除了 debug 程式碼,我們需要做的就只是用眼睛看效果是否 ok,整個流程優化下來,體驗是很讚的!
☞ 解決代理遇到的問題
上面我們提到了三個問題,平時開發遇到最頭疼的一個是 combo ,曾經我們頁面上的程式碼加一個 ?_xxx 引數就能夠直接開始除錯模式,那是因為程式的入口只有一個,而且所有指令碼的依賴也打包到一個叫做 deps.js 檔案中,加上除錯引數之後,可以將原來 combo 載入的檔案: http://example.com/path/??a-min.js,b-min.js,c-min.js ,按照非 combo 的方式載入:
1 2 3 |
http://example.com/path/a.js http://example.com/path/b.js http://example.com/path/c.js |
上面的程式碼可以輕鬆地代理到本地,但是有的系統生成的程式碼並沒有 deps.js 檔案,它是將指令碼直接輸出到頁面上:
1 |
<script src="http://example.com/path/??a-min.js,b-min.js,c-min.js"></script> |
☞ 解決 combo 問題
此時通過 Fiddler/Charles 工具比較難滿足需求,對於這個問題有兩個處理方案:
1). 瀏覽器請求全部代理到本地的一個服務
首先寫一個本地服務:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var http = require('http'); // npm i http-proxy --save var httpProxy = require('http-proxy'); var proxy = httpProxy.createProxyServer({}); var server = http.createServer(function(req, res) { console.log(req.url); if(req.url.indexOf("??") > -1){ // combo資源讓 3400 埠的服務處理 proxy.web(req, res, { target: 'http://127.0.0.1:3400' }); } else { // 直接返回 proxy.web(req, res, { target: req.url }); } }).listen(3399, function(){ console.log("在埠 3399 監聽瀏覽器請求"); }); |
程式碼的意思是,利用 http-proxy 這個 npm 包,代理瀏覽器的請求,瀏覽器上使用 switchSharp 設定本地代理為
http://127.0.0.1:3399 ,當請求過來,先判斷 url,如果 url 中包含了 ??
則將其作為 combo 資源處理,代理給本地的另一個服務
http://127.0.0.1:3400 ,這個服務收到請求後會將 combo 內容分解成多個,全部請求完之後再吐出來。
2). 使用本地服務請求 html 程式碼,替換 html 程式碼內容
使用強制手段(原始碼替換)將程式碼解 combo,比如原始碼頁面為:
1 2 3 |
<!-- html code --> <script src="http://example.com/path/??a-min.js,b-min.js,c-min.js"></script> <!-- html code --> |
使用本地服務請求這個url,然後轉換成:
1 2 3 4 5 |
<!-- html code --> <script src="http://example.com/path/a.js"></script> <script src="http://example.com/path/b.js"></script> <script src="http://example.com/path/c.js"></script> <!-- html code --> |
實現這個操作的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var http = require('http'); // npm i request --save; var request = require('request'); http.createServer(function(req, res){ var path = req.url.slice(req.url.indexOf("path=") + 5); console.log(path); if(!path) { res.write("path is empty"); res.end(); return; } request(path, function (error, response, body) { if (!error && response.statusCode == 200) { console.log(body); // 程式碼替換 body = body.replace('<script src="http://example.com/path/??a-min.js,b-min.js,c-min.js"></script>', '<script src="http://example.com/path/a.js"></script>\ <script src="http://example.com/path/b.js"></script>\ <script src="http://example.com/path/c.js"></script>' ); res.write(body); res.end(); } }); }).listen(3399, function(){ console.log("listening on port 3399"); }); |
比如請求 http://127.0.0.1:3399/?path=http://www.taobao.com ,即可拿到淘寶首頁的原始碼,然後對拿到的程式碼做替換。
☞ 解決程式碼壓縮問題
對於這個問題,建議線上上放兩份原始碼,一份是壓縮原始碼,一份是未壓縮原始碼,當頁面 url
存在 debug
引數的時候,返回未壓縮版本,正常返回壓縮版本。當然,也可以採用上述方式處理問題。
不過,更合理的方式應該是 sourceMap
,前端沒有祕密,壓縮程式碼只是增加了 hacker 的攻擊成本,並不妨礙有能力的 hacker 借系統漏洞入侵。所以可以為原始碼提供一份 sourceMap 檔案。
1 2 3 4 5 6 7 8 9 10 |
var gulp = require('gulp'); var sourcemaps = require('gulp-sourcemaps'); gulp.task('javascript', function() { gulp.src('src/**/*.js') .pipe(sourcemaps.init()) //.pipe(xx()) .pipe(sourcemaps.write()) .pipe(gulp.dest('dist')); }); |
關於 sourceMap 的 gulp 外掛配置,詳情可以戳這裡。不僅僅是 JavaScript,CSS 也有 source maps,這個資訊可以在 Chrome 控制檯的設定選項中看到:
☞ 程式碼的拉取
如果一個專案只有你知道如何修改,那這個專案的技術設計就有點糟糕了,為了讓眾人都能處理你專案中的問題,一定要需要一個簡潔的模式為開發者快速搭建測試環境,文件是一方面,如果有個一鍵操作的命令,那就更棒了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 啟動指令碼 start: createFile getMod getPage # 建立目錄 createFile: @[ -d module ] || mkdir module @[ -d page ] || mkdir page # 拉取模組倉庫,這裡有幾十個,比較費時,請耐心等待... getMod: cd module; \ for i in $(MODS); do \ [ -d $(MODPATH)$$i ] || git clone $(MODPATH)$$i; \ git co -b master;\ git co -b $(MODSV); done # 拉取頁面倉庫,tbindex getPage: cd page; \ @[ -d tbindex ] || git clone $(PAGEPATH)$PAGE; |
上面是一個 MakeFile
的部分程式碼,作用是建立開發目錄,拉取分支資訊,然後開始伺服器,開啟瀏覽器,使用 IDE 開啟目錄,萬事就緒,只等主人敲程式碼。
整個流程就一兩分鐘,完成開發之前所有的準備工作。這個指令碼不僅僅是給自己使用,如果其他人也需要參與開發,一個命令就能讓參與者進入開發模式,加上文件說明,省卻了很多溝通成本。
☞ 線上除錯實踐(一個系統的除錯工具)
輸入需要除錯的頁面URL(如 http://www.taobao.com):
外掛會分析 DOM,遍歷拿到頁面所有被引用到的倉庫:
選擇需要除錯的模組(顆粒度細分到了html/js/css),點選除錯按鈕,可以看到除錯頁面的資源都會引用本地下載的檔案。
☞ 小結
優化流程、優化架構是我們矢志不渝堅持的方向,本文主要闡述,編輯程式碼到除錯線上效果的過程,提出瞭解決 combo 和程式碼壓縮等問題的方案和建議。希望可以給不擅長代理除錯的同學一點啟示。