前言
React Native是一個跨平臺的使用JavaScript編寫最終生成移動端原生控件應用的開源框架。下面是根據React Native在iOS端的情形下,對項目啓動時,所運行的腳本進行解讀。 當咱們使用React Native的命令行工具建立一個項目( react-native init myProject ),在啓動項目項目(執行 react-native start命令 或 使用Xcode打開ios項目,CMD + R 運行項目)時,React Native會默認幫咱們啓動一個名爲Packager的Node服務,同時在iOS項目編譯過程當中還會執行react-natvie-xcode.sh的腳本。下面咱們將對這兩部分的腳本進行一一解讀。node
啓動Packager服務
-
Packager的意義react
它是一個本地的Node服務,咱們在開發過程當中(DEBUG模下)獲取的js代碼實際上是這個服務器返回的。咱們在修改js代碼時,Packager服務會動態的同步咱們的代碼,而後App再去請求Packager服務獲取咱們修改的最新代碼。這一過程就避免了咱們在開發過程當中,頻繁進行打包的過程。Packager爲咱們提供了一個高效的代碼動態加載方式。ios
-
腳本啓動入口shell
-
使用命令行啓動 若是咱們啓動的方式是使用命令行方式啓動的,那麼在這個命令背後作的工做之一就是啓動Packager。啓動命令以下:react-native
react-native run-ios
複製代碼
-
在iOS項目中啓動 若是咱們是經過XCode打開iOS項目,而後在執行快捷鍵CMD + R命令運行項目,此時也會自動的啓動Packager,那麼啓動Packager的入口在哪裏呢?參見Xcode界面的截圖: xcode
經過上面的截圖標註的路徑,咱們能夠看到經過Xcode在編譯React這個工程時,會去執行一段shell腳原本啓動咱們的Packager服務。
-
shell腳本解讀bash
- RCT_METRO_PORT變量爲Packager的服務的端口號,將這個變量賦值8081
- 將RCT_METRO_PORT=8081這段字符串寫入到node_modules/react-native/scripts/.packager.env路徑下
- 判斷指定的端口號(8081)是否被佔用了:
- 若是被佔用了:則向這個端口號發送請求(請求的url:http://localhost:8081/status/),若是返回字符串爲 packager-status:running 則說明Packager已經啓動了。不然就退出腳本,返回退出碼2。(注意:在編譯過程當中,執行的腳本退出碼爲非零,則編譯就會退出並報錯)。
- 若是沒有被佔用:則執行node_modules/react-native/scripts/launchPackager.command路徑下的腳本。爲了更直觀的感覺script文件夾下都有哪些東西,可見下圖:
- launchPackager.command腳本其實只作了很是簡單的事情,只是將打印形式以及終端標題修改,而後再執行同級目錄下的packager.sh腳本。
- packager.sh腳本也僅有幾行代碼。在讀取前面講到的 .packager.env 文件,獲取端口號以後,經過執行Node命令執行與scripts文件夾同級的local-cli文件夾下的cli.js文件,並傳入當前腳本的全部參數。最終達到了與執行命令行時的效果———啓動Packager服務。
-
小結 經過iOS項目啓動Packager服務的腳本解讀,咱們知道了在iOS項目中,啓動腳本的入口在哪,以及層層遞進找到了真正啓動Packager服務的文件在哪裏。在iOS項目中的這些設置,是React Native的命令行在生成項目時,爲咱們作的工做。 若是咱們是在原生項目中集成RN的話,咱們就不得不本身手動作這些操做了。大多數狀況下咱們會使用CocoaPods來管理第三方的依賴庫(固然也包括React Native)。所以對於啓動Packager的腳本放置的位置應該是在Pods工程下了。服務器
執行打包操做
無論咱們是經過命令行啓動App仍是經過Xcode啓動App,最終都會執行編譯的過程。而在編譯咱們項目的Target時,仍會執行React Native幫咱們插入的腳本。腳本的入口以下圖:網絡
咱們能夠看到,在這段腳本中,主要執行了
react-native-xcode.sh文件中的腳本。 下面就針對腳本的執行邏輯進行解讀:
- 獲取項目編譯的路徑,即編譯時生成.app包的路徑
- 根據項目配置(CONFIGURATION)判斷是不是DEBUG模式,同時判斷運行的平臺(PLATFORM_NAME)是否不是模擬器(便是否在真機上)。若兩個條件同時知足,則獲取本機在當前局域網下的ip地址,並將這個ip地址寫入到.app包路徑下的ip.txt文件中。 說明:這個ip.txt會用在真機調試下。在App啓動時,在RCTBundleURLProvider對象尋找jsbundle路徑時用到。此時返回Packager的URL的主機名再也不是localhost,而是從ip.txt中獲取的ip地址
- 判斷環境變量SKIP_BUNDLING是否有值,若是有值,則退出腳本。再也不進行後續的操做了。 說明:這個參數在React Native初始化項目時,並無給咱們在iOS項目中顯式的表達出來。須要咱們本身指定,隱藏的很深啊!
- 針對環境變量CONFIGURATION的值,進行如下邏輯判斷:
- 值爲DEBUG;判斷運行的平臺是不是模擬器。若是是模擬器,則再判斷環境變量FORCE_BUNDLING是否有值,若是有值則繼續走打包的流程;若是沒值,則退出腳本。這樣作的目的是想要判斷是否在模擬器下也進行打包操做(經過FORCE_BUNDLING來判斷)。
- 值爲空"";說明該腳本並非在Xcode編譯時運行的,所以也不必再執行後續的操做了,中止指定腳本,退出碼1。
- 其餘值;說明此時的編譯配置是Release模式,是須要打包成AdHoc包或上傳到AppStore的。此時將腳本中的變量 DEBUG 的值設置爲false(在其餘分支上DEBUG都爲true),這個值在後面執行打包操做時,會做爲參數傳給打包命令。
- 獲取在node_modules文件夾下的react native文件夾的目錄,以及咱們建立的項目的根目錄,而且切換路徑(cd)到根目錄。
- 獲取根目錄下的入口文件名,這個文件名多是index.js(在較高RN版本項目中),也多是index.ios.js(在較低RN版本項目中)
- 判斷node環境是否存在,若是不存在的話,就退出腳本,退出碼2,編譯報錯。
- 獲取執行打包命令的文件cli.js路徑,便是/react native/cli.js路徑
- 獲取打包的命令;便是 bundle 命令
- 執行打包命令;即運行node,執行cli文件,將以前準備的參數拼接好傳遞進去。 打出來的main.jsbundle以及asserts文件夾都會放在編譯生成的.app文件夾下,也便是放入到App包內了。
小結:app
經過以上流程,咱們知道了在iOS項目中,執行打包腳本的入口在哪裏,以及打包腳本react-native-xcode.sh內部作了哪些操做。其實歸根結底也就執行了咱們平常使用命令行工具打包的 bundle命令,打出來的jsbundle和asserts文件直接放入到了.app包內了。
react-native bundle --entry-file index.js --platform ios --dev false --bundle-output "導出路徑/main.jsbundle" --assets-dest "導出路徑"
複製代碼
這一打包腳本中,值得咱們關注的幾個變量:
- SKIP_BUNDLING: 是否跳過打包操做。 咱們在真機調試的過程當中,通常都會將手機的wifi鏈接到與電腦同一局域網下(也能夠說是同一wifi下),這樣手機才能訪問到Packager服務,進而獲取服務器的js代碼。若是調試的手機的網絡和Packager服務(一般是運行項目的電腦)不是在同一局域網下,那麼運行App時,因爲訪問不到Packager服務器,在請求失敗後,App就會加載咱們在編譯過程生成的js包,從而避免崩潰。這套流程很是完美的解決了咱們在真機調試下,無論手機處於什麼網絡,都可以正常的啓動App了。 儘管React Native全力以赴的幫咱們解決在開發中咱們作得不規範操做所致使的App運行不起來的緣由。可是這樣也給咱們帶來了額外的開銷。 在大多數開發狀況下,咱們進行真機調試時,都會意識到將手機的網絡設置成與電腦的網絡處於同一局域網下(通俗點講是:要設置成同一wifi下)。咱們明確咱們的目標是讓App接連到Packager服務,而並不是本地的jsbundle代碼。可是咱們卻意外的承受了打包jsbundle所帶來的額外時間開銷。 從另外一方面來講,假設咱們的手機處於與Packager服務不一樣的局域網下,在App啓動時,會發送請求去驗證Packager服務是否正常運行,在等待相應的過程當中是異常的漫長,默認狀況下咱們須要等待60秒後(請求超時),返回結果失敗了,纔會去加載本地的jsbundle代碼,App才能啓動。此時咱們花費的等待時間就太漫長了。 若是開發者不肯意承受這種額外的開銷。那麼SKIP_BUNDLING變量就給了咱們避免額外開銷的一種解決方式。咱們只須要在iOS項目執行打包腳本的入口將SKIP_BUNDLING的值設置爲true便可。代碼以下:
export SKIP_BUNDLING=true
複製代碼
最終入口的截圖:
2.
FORCE_BUNDLING:該變量用來代表在模擬器上運行時,是否也要進行打包操做。 咱們已經知道App啓動時,去哪裏加載js代碼的規則。有些時候,咱們想在在模擬器上直接運行咱們的App,可是咱們又想啓動Packager服務。那麼咱們就可使用這個變量來讓咱們在編譯App時,也須要將js代碼打包到模擬器的.app包內。就能夠達到這樣的目的了 設置的方法如同前面介紹的方法同樣。只須要將
export FORCE_BUNDLING=true 代碼放入到iOS項目執行打包腳本的入口便可。
總結
咱們介紹了React Native在iOS平臺下,在啓動App時所執行的腳本。對於平常開發具備哪些指導意義呢?
- 在原生項目想要集成RN時,咱們介紹了應該如何去完善本身手動集成RN時,缺失的那部分腳本設置。
- 在優化編譯速度以及咱們的開發效率上,提出了幾點意見。
- 對於瞭解RN的底層邏輯,方便咱們往後問題排查和個性化定製提供基礎。