襁褓中的 deno (番外):現狀與展望

襁褓中的 deno (番外):現狀與展望

來自專欄兜兜轉轉的代碼生活

這是『襁褓中的 deno』系列的第三篇,也是個番外篇。很早就想寫一篇 deno 的評論,這次也是受到了 為什麼我仍然認為deno是下一代node 和 Deno不是下一代Node.js!這兩篇文章的啟發,希望能從一個一直 track 項目的 contributor 的角度來聊一聊 deno。

TL;DR:目前的 deno 完全沒有能力做 『下一代 Node』 ,但是由此引發的改革卻很有可能顛覆 Node 的統治

PS:??的文章都是在解釋??這句觀點,趕著過周末的盆友可以撤了(溜了溜了


什麼是下一代 Node?

原先的問題(『deno 是下一代 node 么?』)本身是很值得商榷的,其中主要的爭議點就是:

什麼是『下一代 Node』?

為了方便討論,我在這裡把這個概念暫定為:

更先進的非瀏覽器運行時下的『以JavaScript為子集的語言』的運行時。

這裡的『更先進』其實也是一個 magic words,不過為了簡化問題,我們姑且可以認為『更先進』代表著某種更易於接受的實踐

OK,那確定了原問題的一些爭議點,我們現在可以來看一看『去糖』後的問題:

deno 目前的設計是否支持 TA 成為更易於接受的非瀏覽器運行時下的『以JavaScript為子集的語言』的運行時呢?

我的答案是什麼?

講道理作為喜歡在 issue 區吹水的 contributor,我對 deno 還是蠻有感情的,但是在這個問題上,我不得不說:

No!

只給一個結論確實讓人有點摸不著頭腦,所以我想從 現狀 展望 這兩個角度來聊一聊 deno 目前的爭議焦點,以饗讀者。


現狀:性能捉急

當談到 runtime 的性能時,我們一般分為兩個部分來討論,一個是啟動性能,一個是運行性能,從這兩個角度來看,deno 與 Node 相比都略顯吃虧。

deno 運行的時候除了需要啟動一個 v8 以外,還需要去啟動一個 TS 的 compiler,並把源代碼轉譯成 JavaScript,這在項目規模逐漸增大的時候可能會影響到 deno 的啟動速度,更令人頭疼的是,就算我們可以通過緩存業務代碼的編譯結果以消除編譯時間,TS compiler 的啟動開銷仍然不可小覷。

而 deno 的『肉夾饃結構』更是一個性能難點,每一次的系統調用都需要通過 c++ 來觸發定義在 Golang 中的接收函數,眾所周知 FFI 的調用成本,emmmmm... 相比 Node 的 C++ binding,deno 的 CGo binding 更讓人捏一把冷汗,尤其是在 server 端要面對高並發的場景下,大量的 socket call 可能會讓這種結構的劣勢盡顯無疑。

展望:善其事,利其器

針對上面所提到的性能痛點,其實還是有一些解決方案的。

對於啟動性能來說,我們可以針對不同類型的文件來提出不同的優化手段。如果是在開發環境中運行 .ts文件, 那我們可以向 V8 學習,定製 TS compiler 以引入 snapshot 機制;如果是在線上環境中運行 .js文件,那我們完全可以定義一個 FastPath,不啟動 TS compiler 而直接啟動 deno。這種優化手段下,能讓 deno 的啟動性能完全不遜於 Node。

如果說啟動性能無論如何都沒辦法和 Node 媲美,那在運行性能的優化上則是有很多文章可以做。從 Ryan 大神的意見來看,現在採用 CGo 僅僅是為了快速實現原型而已,至於項目真正走上了正軌,這一層 binding 完全可以選擇更高性能的 C++ 或者 Rust(最近在 issue 裡面呼聲非常高)。

上面的優化都是可行性極高的(甚至有些正在進行),但既然是展望,那我們不妨去『想像』一下。既然我們的源語言是一門靜態語言(TypeScript),那在編譯階段能做的事情就非常非常多了。我們可以在編譯到 JS 之前就做一層代碼優化,類似 prepack,不過相比 JS 效果會更好;更有甚者,去重新構建一個 TS Engine 也未嘗不可,雖然成本巨大,但是能帶來的性能提升也遠非動態優化下的 JS 所能媲美的,前端甚至有可能迎來真正的「高性能時代」。

PS:剛剛從 issue 發回的 Ryan 最新消息:

I have a private branch that is exploring snapshotting + better build system only in c++, I think that might become the core system where the message passing between V8 and the outside world can be unit tested. Once thats working I will experiment with binding to Rust.

我新開了一個私人分支,並在上面嘗試使用 snapshot 和純 C++ 來構建更棒的系統。我認為這可能成為 V8 和外部世界之間溝通的核心系統,並且能夠被單元測試。一旦這套系統奏效,我會寫一個 Rust 的 binding。

The reason for not using Go is that it has a rather complex runtime - including a GC. Although I havent experienced any problems with that yet, its not hard to imagine that down the road that might clash badly with V8s very complex runtime.

不使用 Go 的原因是它有一個相當複雜的運行時- 比如說 GC 。 雖然我還沒有遇到過任何問題,但不難想像,由於 V8 也有著非常複雜的運行時,這兩者可能會發生嚴重的衝突。


現狀:依賴解決方案爭議頗大

在 Ryan 大神的 pre 中,他專門對 Node 中 npm 的 package.json 做了非常深入的探討,並給出了在 deno 中的解決方案:

  • 本地的依賴將中心化,不再會有多個 node_modules,依賴將統一存放在 ~/.deno中。
  • 依賴必須明確文件的拓展名
  • 引入依賴的方式為 URLs,而不再是原來的 alias,將模塊搜索的二義性轉化成編碼複雜度。

對於熟悉 Go 的人來說,這個方案其實就是 Go 依賴管理的一個拓展,但是這個方案仍然有比較大的爭議,比如說如果在代碼硬編碼依賴版本的話,如何去做統一的依賴管理呢?使用 URL 的話,https 和 http 該如何取捨?是否應該引入可配置化的 base-dir 來降低相對路徑的編寫複雜度?諸如此類問題在 issue 中也是自成一派了。

展望:取眾長,補己短

對於上面的那些問題,其實我們可以參考一些現有的解決方案,博取眾長。

比如雖然原先的 package.json 含有過多的信息,但是這種集中式的本地依賴管理還是很行之有效的,那我們就不妨效仿 vgo,大幅簡化依賴配置文件中的信息,僅保留關鍵的 alias 和語義化版本,在無二義性匹配和靈活配置中找到一個平衡點。

不過依賴解決方案目前還沒有銀彈,只有在使用的過程中才會發現更多的可優化點,只能希望 deno 距離 MVP 更近一點了。


後記

從上面可以看出,deno 是一個非常非常初級的原型,它的作用也僅限於驗證在類似模型下編寫運行時的可能性與簡單的運行效率測試。所以就目前來看,deno 要想成為 Node 的接班人還有很多路要走。

但是瑕不掩瑜,deno 的不少創新點之於 Node 確實是非常棒的改進,在 issue 區也有很多有創意的討論,這部分可能會被 Node 吸收,也可能會催生出真正的『下一代 Node』。

所以在面對 deno 的時候,忘掉什麼『下一代 Node 』這種說法吧。為什麼不靜下心來,看看 TA 到底帶給了我們什麼有價值的東西呢?

推薦閱讀:

php 和nodejs 的各自優勢有哪些,如果無基礎開始,要學那個好?
為什麼nodejs不給每一個.js文件以獨立的上下文來避免作用域被污染?
nodejs寫後端的時候如果需要添加功能是不是只能將nodejs服務停止才行?
Nodejs中的非同步到底是優勢還是劣勢?
你用 Node.js 寫過哪些大型/複雜的應用?碰到什麼難點?

TAG:Nodejs | TypeScript | 前端開發 |