讓 React Native 更多地利用 Node.JS 的資產(chǎn)

在一年多前的文章 “Javascript 的前后端統(tǒng)一是個"笑話”嗎?”中雀彼,我介紹了如何在 Web 前端復(fù)用 Node.JS 中的設(shè)計思想和代碼湾蔓。這一年的時間洲劣,JavaScript 除了在 Web 前端這一領(lǐng)域繼續(xù)保持著統(tǒng)治地位瓢颅,同時也真正深入到了 Native Client 的開發(fā)領(lǐng)域。這得益于 React Native (下文簡稱 RN) 所采用的全新的工作方式熙兔。

那么很自然地悲伶,能否在 RN 項目中像 Web 前端一樣復(fù)用 Node.JS 的代碼呢?經(jīng)過一段時間的研究和實驗住涉,答案是可以的麸锉。但是有一些概念點需要先搞清楚。在描述這些概念之前舆声,讓我們先跑通一個 Demo花沉。

在此之前,我先假設(shè)讀者已經(jīng)有安裝媳握、創(chuàng)建主穗、執(zhí)行 RN 程序的經(jīng)驗。如果沒有毙芜,那就請先參考 Facebook 的文檔 https://facebook.github.io/react-native/docs/getting-started.html

Demo

Demo 的執(zhí)行分為 Server 和 Client 兩部分争拐。

Server Side

首先安裝 Server 部分 https://github.com/jacobbubu/mux-dnode-butt

git clone https://github.com/jacobbubu/mux-dnode-butt.git
cd mux-dnode-butt
npm install
npm start

這是很常見的 Node.JS 應(yīng)用的執(zhí)行流程腋粥。這個 Server 是一個 WebSocket Server,在一條 WebSocket 鏈路上架曹,通過多路復(fù)用模塊隘冲,提供了dnode 協(xié)議的 RPC 服務(wù)器,以及 scuttlebutt 協(xié)議的多節(jié)點同步服務(wù)绑雄。

如果你只是關(guān)心如何讓 RN 應(yīng)用利用 Node.JS 的代碼展辞,那么無需關(guān)注其中的概念。如果想了解其中的原理万牺,那么首先需要有 Node.JS stream 的知識罗珍。其次對于 dnode洽腺,你可以參考原始項目,或者先看看我翻譯的協(xié)議文檔覆旱;對于 scuttlebutt蘸朋,也可以看我的 Fork 來入門。

至于為什么用這樣的例子扣唱,是因為這對我的項目的架構(gòu)模式很重要藕坯,如果可行將可以降低我的開發(fā)成本。

執(zhí)行起來看起來如下圖:


Server 輸出

Client Side

客戶端程序在 https://github.com/jacobbubu/rnMuxNodeButt :

git clone https://github.com/jacobbubu/mux-dnode-butt.git
cd mux-dnode-butt
npm install
npm start

執(zhí)行起來如下圖噪沙,看起來應(yīng)該和傳統(tǒng)的 RN Packager 不太一樣炼彪,多了前面的綠色輸出。

?Packager 輸出

然后用 Xcode 打開 ios/rnMuxNodeButt.xcodeproj 正歼,且執(zhí)行辐马。如果之前 Server 可以正常執(zhí)行,那么你將看到模擬其中的執(zhí)行結(jié)果:
?iOS Client

一條 WebSocket 鏈路會建立起來朋腋,隨后 Client 會發(fā)起一個 RPC 調(diào)用齐疙;同時,一個狀態(tài)同步的 Stream 也會建立起來旭咽,Server 不斷地將自己的時鐘同步給它的訂閱者贞奋。你可以瀏覽一下 Server 和 Client 的代碼,有個初步的感覺穷绵。

如何做到?

package.json 中的 browser 字段

其實在 Server Repo. 中是包含 Web 前端的例子的轿塔,你只要訪問 http://localhost:9999,能看到一個”粗糙”的 Web 頁面(腳本代碼在 src/client.coffee 或者 lib/client.js)演示了類似的功能仲墨。這段前端代碼是通過 browserify 實現(xiàn)的勾缭。 browserify 會遍歷你的代碼的模塊依賴關(guān)系,同時提供 Node.JS 的核心模塊的 Mock目养,這樣就為大部分 Node.JS 模塊提供了一個在瀏覽器中的 Node.JS “仿真”環(huán)境俩由,從而可以執(zhí)行。

當然癌蚁,并非所有模塊都能夠這樣”天真”地執(zhí)行幻梯。例如 ws 模塊,當其在 真正的Node.JS 環(huán)境中執(zhí)行時努释,它需要實現(xiàn)一個完整的 WebSocket 的 Client 的功能碘梢。而在瀏覽器中,則僅僅需要簡單地封裝一下 DOM WebSocket 對象即可伐蒂,沒有必要也不可能在瀏覽器中從頭實現(xiàn) WebSocket Client煞躬。

這就需要提供一個約定,讓模塊開發(fā)者能夠分別提供運行在真正的 Node.JS 環(huán)境中的版本和運行在瀏覽器中的版本。browserify 約定恩沛,如果模塊開發(fā)者在其 package.json 中提供 Browser 字段在扰,那么就將使用該字段中配置的版本,以 ws 模塊為例复唤,其 package.json 中對應(yīng)的配置為:

{
  ...
  "browser": "./lib/browser.js",
  ...
}`

在上面的例子中健田,ws 模塊告訴 browserify,當其在瀏覽器中使用時需要用 ./lib/browser.js 的實現(xiàn)替代缺省的實現(xiàn)(package.jsonmain 字段的定義)佛纫。

Browser 字段的規(guī)范定義在 https://github.com/substack/node-browserify#browser-field 妓局,其值也可以定義為一個 Hash Object,例如:

"browser": {
  "fs": "level-fs",
  "./lib/ops.js": "./browser/opts.js"
}

在這個例子中呈宇,所有對于 fs 模塊的引用都將被 level-fs 所替代好爬。level-fs 是在 levelup 接口之上 Mock 了 fs 的方法,而 levelup 是可以采用多種存儲引擎的甥啄,從內(nèi)存到 Web Storage存炮,因此就可以在瀏覽器中執(zhí)行了。

完整的規(guī)范在 https://gist.github.com/defunctzombie/4339901 蜈漓。

已經(jīng)有很多 Node.JS 的模塊遵循這個規(guī)范來提供對應(yīng)的瀏覽器版本(如果需要的話)穆桂,以提升全棧開發(fā)的生產(chǎn)效率。為了利用好這部分資產(chǎn)融虽,WebPack 也缺省支持這個規(guī)范享完。這也算是一個事實上的”標準”吧。

Node.JS Core Modules 和全局變量

browser 字段規(guī)范確保了模塊可以提供瀏覽器執(zhí)行的版本有额。但是我們還需要為每個模塊提供一個 Node.JS 的仿真環(huán)境般又。”欺騙”一個模塊并不復(fù)雜巍佑,你只要做好兩件事即可:

  1. 確保該模塊能夠找到所有(或者常用的)的 Node.JS Core Modules
  2. 確保該有的 Node.JS 的全局變量都在茴迁,例如:Buffer、process, module 等萤衰。

Mocks of Core Modules

再次感謝 browserify 的貢獻堕义,你可以在 這里 看到所有 Node.JS Core Modules 的 Mocks。如果你用 browserify, 這些 Mocks 會被自動應(yīng)用脆栋。如果用的是 WebPack, 那么可以通過在 webpack.config.js 配置 resolve.alias 來完成(https://github.com/jacobbubu/rnMuxNodeButt/blob/master/webpack.config.js#L22 )胳螟,其中 Mocks 的定義是通過 https://github.com/webpack/node-libs-browser 完成的。

全局變量

browserifyWebPack 都會默認配置好 Node.JS 需要的全局變量(https://webpack.github.io/docs/configuration.html#node )筹吐。它們沒有配置的也會留給瀏覽器或者 RN Packager 來完成。

DOM Polyfills

要讓 browserifyWebPack 生態(tài)圈的代碼運行在 RN 中的最后一點要求就是秘遏,需要能 Polyfills 常用的 DOM 對象:例如:window丘薛、WebSocket、XMLHttpRequest 等邦危。這一點洋侨,RN 目前還是有一些欠缺的舍扰。這個欠缺主要在于,RN 并沒有按照規(guī)范完整地實現(xiàn)希坚。例如边苹,WebSocket、XMLHttpRequest 就都沒有實現(xiàn) EventTarget 接口裁僧,這樣使用者就不能通過 addEventListener 或者 removeEventListener 來添加刪除事件響應(yīng)函數(shù)个束,而這在瀏覽器代碼中是約定俗成的。

Facebook 知道這個問題( https://github.com/facebook/react-native/issues/2583 )聊疲,但是由于其自身的需求沒有用到茬底,因此把其標注為 Community Responsibility,請社區(qū)來實現(xiàn)获洲。

WebSocket 的EventTarget 已經(jīng)在 RN 的 0.13-RC 中實現(xiàn)https://github.com/facebook/react-native/pull/2599

在我的例子里阱表,原來用到的 websocket-stream 遇到了這個問題,因此我 fork 了一個 rn-websocket-stream 以解決這個問題贡珊。這不是個”治本”的方法最爬,但是對我的例子夠用了。

另外 RN 的 WebSocket Polyfill 也沒有實現(xiàn) ArrayBuffer 的二進制傳輸门岔,不過也不影響我的使用爱致。

RN 的 Packager 問題

RN Packager 完成了類似 browserify 或者 WebPack 的功能,即固歪,對代碼進行轉(zhuǎn)換(通過 Babel)蒜鸡,通過分析模塊間的 require 生成依賴樹,將模塊打包到一起(生成 Bundle)牢裳,優(yōu)化代碼(如果發(fā)布的話)逢防。最后通過一個內(nèi)置的 Web Server 提供打包后代碼的下載,這使得 Xcode 中的 Obj-C 代碼可以有機會動態(tài)下載 JS Bundle 來執(zhí)行蒲讯。

RN Packager 沒有用 browserify 或者 WebPack 的理由是忘朝,當年(RN 開始的時候)WebPack 生成 Bundle 的性能不夠好,所以就重新發(fā)明了”輪子”判帮。

相對 browserify 或者 WebPack 功能的完善度和生態(tài)圈的完整性局嘁,RN Packager 還差得很多,這也是為什么今后 RN Packager 將從 React Native Repo. 中獨立出來的原因之一晦墙。

目前 RN Packager 僅僅實現(xiàn)了非常有限的 Browser 字段的規(guī)范悦昵,離完整的規(guī)范還差得遠。因此可以說晌畅,根本就不能直接利用 browserifyWebPack 生態(tài)圈的成果但指,

RN Packager 也沒有提供 Node.JS 全局變量的設(shè)置,不過這個相對簡單,只要在其他代碼執(zhí)行前加一些 Shim Code 就可以了:

...
if (typeof __dirname === 'undefined') global.__dirname = '/'
if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer
...

所以棋凳,我們需要在 RN Packager 之前再引入一層 Packager拦坠,react-native-webpack-server 就是用來解決這個問題的。

react-native-webpack-server

react-native-webpack-server 的使用文檔請參見 https://github.com/mjohnston/react-native-webpack-server 剩岳。

通過 react-native-webpack-server贞滨,原本一次的 Packaging 的過程變成兩次。當 Obj-C 請求代碼時拍棕,需要向 react-native-webpack-server 啟動的 WebServer(缺省監(jiān)聽 8080 端口) 發(fā)出請求晓铆。react-native-webpack-server 先通過 WebPack 來打包代碼,生成臨時的 index.ios.js莫湘。

react-native-webpack-server 在啟動時尤蒿,也會同時啟動 RN Packager (缺省監(jiān)聽 8081 端口)。RN Packager 會監(jiān)控 index.ios.js幅垮,一旦發(fā)生變化腰池,就會重新打包。

react-native-webpack-server 在生成 index.ios.js 之后忙芒,隨之就會向 RN Packager 請求打包的最終結(jié)果示弓,得到之后傳回到 Obj-C 請求者。

react-native-webpack-server 在第一次打包時呵萨,也會通過 WebPackExternals 設(shè)置奏属,躲過RN 相關(guān)的模塊,交給 RN Packager 來處理潮峦。

通過這種方式囱皿,可以充分利用 WebPack 生態(tài)圈的豐富資產(chǎn),遠遠超過 RN Packager 所能提供的功能集合忱嘹。

總結(jié)

文章不長嘱腥,例子也不復(fù)雜,但是做到這一步還真是花了些時間填坑拘悦。記錄下來用于今后回憶齿兔,否則半年后可能就忘光了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末础米,一起剝皮案震驚了整個濱河市分苇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屁桑,老刑警劉巖医寿,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蘑斧,居然都是意外死亡糟红,警方通過查閱死者的電腦和手機艾帐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盆偿,“玉大人,你說我怎么就攤上這事准浴∈屡ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵随橘,是天一觀的道長掌唾。 經(jīng)常有香客問我钮莲,道長,這世上最難降的妖魔是什么罐农? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮催什,結(jié)果婚禮上涵亏,老公的妹妹穿的比我還像新娘。我一直安慰自己蒲凶,他們只是感情好气筋,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旋圆,像睡著了一般宠默。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灵巧,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天搀矫,我揣著相機與錄音,去河邊找鬼刻肄。 笑死瓤球,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的肄方。 我是一名探鬼主播冰垄,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼权她!你這毒婦竟也來了虹茶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤隅要,失蹤者是張志新(化名)和其女友劉穎蝴罪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體步清,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡要门,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年虏肾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欢搜。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡封豪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炒瘟,到底是詐尸還是另有隱情吹埠,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布疮装,位于F島的核電站缘琅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏廓推。R本人自食惡果不足惜刷袍,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望樊展。 院中可真熱鬧呻纹,春花似錦、人聲如沸滚局。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽藤肢。三九已至太闺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘁圈,已是汗流浹背省骂。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留最住,地道東北人钞澳。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像涨缚,于是被迫代替她去往敵國和親轧粟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容