ReactNative 知識小集(1)-深入理解 React Native Debugging

翻譯改編自 Shaheen Ghiassy的Deep Diving React Native Debugging from medium.com

中文譯名: 深入理解 React Native Debugging

譯者注: 使用React Native(以下簡稱RN) Debug更是每天都會用到的技能.最近閑來看看一些原理性的文章,決定先從debug入手,可以更深入了解到框架背後的東西.原作者寫這篇文章的時候RN還是0.11版本,我的開發版本是 0.47.其實debug的原理沒有改變,只是個別環節的實現function增加了一些參數.下面我們開始正文


如果你和我一樣,當你看到React Native 簡單的debugging的時候,你一定會覺得這太拽了.在Chrome瀏覽器里打下斷點,就可以終端iOS應用.而且可以console和watch各種變數.

但如果你和我一樣,你一定不會相信編程中有魔法的存在,而要去探究他的工程實現.這篇文章就是帶你理解React Native Debugging 背後的原理.

tl;dr 文章很長,不喜勿讀

三方清單

  1. 一個NodeJS server. 稱為 Packager. 運行在 terminal中
  2. Chrome 瀏覽器從Packager載入你的React Native JavaScript,並提供內置的debugging 支持,就像 debug 普通的JavaScript代碼一樣
  3. 設備上跑的App 和 在瀏覽器中跑的js應用 通過webSocket 通信. 設備和瀏覽器互相通過JSON命令通訊,瀏覽器來控制設備. 當然站在瀏覽器背後的是這個 Packager.

下面就來詳細講述這三方的關係.

深入理解

如果你想繼續了解,你會對和debugging有關的過程,文件和操作更感興趣.

Note: React Native 發展迅速, 本篇文章基於 React Native 0.47 版本

讓我們先來直觀的感受一下上面提到的三方+你的代碼

* 一個典型的react應用中的各個角色

這四方之前的關係圖有點小複雜,我們會慢慢展開.Facebook和一些第三方都在致力於開發一站式的開發工具,例如[Nuclide IDE](Nuclide), [Deco Sofeware](Deco - React Native IDE),但無論怎麼說,我們還是探究一下各方的細節.

A. 你的移動設備: native code 運行在上面

B. NodeJS Server: Facebook』s [Packager](github.com/facebook/rea) NodeJS Server. 一個類WebPack的項目,有個基於 CommonJS 的模塊系統, 和一堆可用的黑科技.用來幫助 React Native 開發

C. Chrome 瀏覽器: 這個不用介紹了吧

D. React Native JavaScript code: 你的價值體現

那麼究竟在 Debugging 時發生了什麼

Step 1: Start Packager

如果你在命令行中輸入: 「npm start」. 那麼他會觸發 react-native/local-cli/cli.js 去執行start.這個在 項目的package.json 中可以看到. 包括在XCode > Build Phases > React-Native shell > react-native-xcode.sh 最後執行的也是 cli.js. 然後他會起一個 NodeJS Server,基於 Connect 開發的.

runServer.js?

gist.github.com

順序是這樣的

  1. Packager 打包好並host localhost:8081/index.io
  2. packager 觸發 localhost:8081/launch-c
  3. chrome打開 localhost:8081/debugger

[Terminal 中的呈現]

同時 Packager 會配置一個 WebSocket 的服務,這個稍後介紹.

tips: webSocket 是附著在http協議上的. http://localhost:8081 ws://localhost:8081

Step 2: Run React Native App in Simulator

在XCode中 run,然後模擬器中會呈現. 實測是這樣的, 跳過Step 1.直接在Xcode中run,系統會自動默認的Terminal,然後跑 npm start. 在真機調試一樣也會啟動.

程序跑起來了

真機調試 Tip: 在用真機的時候,ws 服務的地址要換成 筆記本的IP,不能用localhost

要不iOS設備在Wifi環境下訪問不到 ws服務.

Automated IP Configuration for React Native Development - Modus Create?

moduscreate.com圖標

一旦App在設備上跑起來,他會去訪問 localhost.com:8081/inde. 並把它轉換成原生的代碼執行. 想看React Native原理可以參考

React Native通信機制詳解?

blog.cnbang.net圖標React Native之源碼分析?

slides.com

Packager 是跑在本地的,一旦他收到瀏覽器或者App的請求,他會使用Babel和Facebook自家的grapher 去collect,concat,transplie and modularize,你的 React Native JavaScript (D) 代碼編程一個 response. 所以當你改動代碼並保存的時候你會看到 Terminal 中會重新 transforming.

Packager process

訪問 localhost:8081/index.io, 看一下package之後的代碼

當iOS設備收到代碼的時候, 它會把JavaScript 代碼放到 Apple』s JavaScript Code 中去執行.

Step 3: Turn on Debugging Mode

現在App在設備上正常的跑著,下一步就是進入 debug 模式,這樣就可以通過Chrome的webSocket inspector 去查看 React Native 編譯過的版本 和 未編譯版本 之前傳輸的數據了.

Chrome webSocket inspector

在模擬器上按 Command+D.會彈出調試選項.真機調試的時候搖一搖也會出現

React Native』s on-screen Developer menu

當你點擊Debug in Chrome 的時候,客戶端會發起一個請求 localhost:8081/launch-c.然後Packager會接到這個請求,然後通過 sindresorhus/opn 執行兩個操作.

第一: 打開Chrome 的一個tab

第二: 讓這個tab打開URL localhost:8081/debugger

Debug in Chrome process

Step 4: Debugger-ui.html and Device establish a connection

現在我們有了一個Chrome Tab,url 是 localhost:8081/debugger 這個頁面的靜態文件是debugger-ui.html.這個網頁會有一個請求里通過. websocket 和 Packager 保持通訊.

https://gist.github.com/mactive/47fd28ec51037026ae4364cdcf633cbb?

gist.github.com

你的App會發送3個WebSocket ping到你的Chrome. 如果Chrome反饋給你 expectedId/sessionId,那麼鏈接將會馬上建立.否則設備會拋出一個紅屏」Runtime is not ready」.相信做過React Native 開發的對這個紅屏不會陌生.一般 Command+R 一下就可以解決.你的App和瀏覽器之前的交互中間都會經過 NodeJSServer.因為http和ws服務都是他運行的.

Step 5: Execute Application Script

鏈接建立之後.App將會發送 WebSocket 消息到瀏覽器,去載入 JavaScript code.

https://gist.github.com/mactive/3cb2e2c53c3b9edf48a0793dae3c82cf?

gist.github.com

發送的這個消息非常有意思,發送的時候回注入一個字典.字典中有很多 module/method/id 的映射關係.這裡的module/method 就對應RCTModule 和 RCTMethod. 這個字典在chrome中也有體現(下圖).一個真實項目中的字典可以參見附錄A

這個啟動消息被運行在Chrome的debugger-ui接受到.消息被插接之後放入Chrome的window對象的「__fbBatchedBridgeConfig」屬性中.

[chrome 中的 window.__fbBatchedBridgeConfig]

https://gist.github.com/mactive/c17a2dca25b3711e32c713a9f0006e9b?

gist.github.com

當Chrome的window對象更新完之後.瀏覽器會載入 bundlejs.

然後debuggerWorker 會吧bundlejs+sourcemap 再還原成一個一個的文件供我們debug,在chrome中可以用 Command+P 來定位文件

  • bundlejs 後的文件: localhost:8081/index.io
  • sourcemap: http://localhost:8081/index.ios.map?platform=ios&dev=true

Execute flow

debuggerWorker sourcemap

從上圖可以看出,App和Chrome之間的交互離不開Packager,最後Chrome中的網頁會告知executeApplicationScript webSocket消息已經發出

Step 6: Run

當 executeApplicationScript 被設備成功接受到之後.App就會重新載入js,呈現給我們.感覺Chrome和App之間通過WebSocket通信,屌屌的. 至此你可以在Chrome像調試網頁的JS一樣調試ReactNative的js了.不過請注意很多第三方node_module的東西無法console出來.就算是在下斷點的時候.比如lodash,具體原因不清.

結語

令人值得稱道的一點是, React Native js在Chrome中的運行方式和 React Native js在設備上 JavaScriptCore 的運行方式很像.都是有個 Module和method的巨大的映射表.

例如 window.__fbBatchedBridgeConfig 在設備上也有類似的mapping.這個相似點很棒.這給我們提供可一種觀察JavaScriptCore的方案.

希望本文能夠幫你更好的理解 React Native, 並寫出更好的應用.

附錄和其他

  • 一份完整的符號表
  • 使用VSCode, 在編輯器中Debug ReactNativeApp
  • 開啟MessageQueue.spy()

import MessageQueue from react-native/Libraries/BatchedBridge/MessageQueue

MessageQueue.spy(true);

接下來會繼續翻譯和撰寫更多的原理方面的文章.


推薦閱讀:

國內有哪些上線的react-native作品?
React、Angular和Vue.js三者中哪一個更易學,更容易理解?
如何評價 React Native Android?
移動應用開發入門,是否可以考慮先學習 React Native?
react native 真機調試apk無法安裝到設備?

TAG:ReactNative | 軟體調試 |