者|狼叔(阿里前端技術專家,Node技術布道者)
編輯|覃云
你好,我是阿里巴巴前端技術專家狼叔,在上一篇文章中,我分享了大前端的現狀和未來,接下來的這篇文章,我將會分享一些大前端跟 Node.js 結合比較密切的點。
Node.js 在大前端布局里意義重大,除了基本構建和 Web 服務外,這里我還想講兩點。
首先它打破了原有的前端邊界,之前應用開發只分前端和 API 開發。但通過引入 Node.js 做 BFF 這樣的 API proxy 中間層,使得 API 開發也成了前端的工作范圍,讓后端同學專注于開發 RPC 服務,很明顯這樣明確的分工是極好的。
其次,在前端開發過程中,有很多問題不依賴服務器端是做不到的,比如場景的性能優化,在使用 React 后,導致 bundle 過大,首屏渲染時間過長,而且存在 SEO 問題,這時候使用 Node.js 做 SSR 就是非常好的。
當然,前端開發 Node.js 還是存在一些成本,要了解運維等,會略微復雜一些,不過也有解決方案,比如 Servlerless 就可以降級運維成本,又能完成前端開發。直白點講,在已有 Node.js 拓展的邊界內,降級運維成本,提高開發的靈活性,這一定會是一個大趨勢。
2018 年 Node.js 發展的非常好,InfoQ 曾翻譯過一篇文章《2018 Node.js 用戶調查報告顯示社區仍然在快速成長》。2018 年 5 月 31 日,Node.js 基金會發布了 2018 年用戶調查報告,涵蓋了來自 100 多個國家 1600 多名參與者的意見。報告顯示,Node.js 的使用量仍然在快速增長,超過?的參與者期望在來年擴展他們的使用場景,另外和 2017 年的報告相比,Node 的易學程度有了大幅提升。
該調查遠非 Node 快速增長的唯一指征。根據 ModuleCounts.com 的數據,Node 的包注冊中心 NPM 每天會增加 507 個包,相比下一名要多 4 倍多。2018 年 Stack Overflow 調查也有類似的結果,JavaScript 是使用最廣泛的語言,Node.js 是使用最廣泛的框架。
本節我會主要分享一些跟 Node.js 結合比較密切的點:首先介紹一下 API 演進與 GraphQL,然后講一下 SSR 如何結合 API 落地,構建出具有 Node.js 特色的服務,然后再簡要介紹下 Node.js 的新特性、新書等,最后聊聊我對Deno 的一點看法。
書本上的軟件工程在互聯網高速發展的今天已經不那么適用了,尤其是移動開發火起來之后,所有企業都崇尚敏捷開發,快魚吃慢魚,甚至覺得 2 周發一個迭代版本都慢,后面組件化和熱更新就是這樣產生的。綜上種種,我們對傳統的軟件工程勢必要重新思考,如何提高開發和產品迭代效率成為重中之重。
先反思一下,開發為什么不那么高效?
從傳統軟件開發過程中,可以看到,需求提出后,先要設計出 ui/ue,然后后端寫接口,再然后 APP、H5 和前端這 3 端才能開始開發,所以串行的流程效率極低。
于是就有了 mock api 的概念。通過靜態 API 模擬,使得需求和 ue 出來之后,就能確定靜態 API,造一些模擬數據,這樣 3 端 + 后端就可以同時開發了。這曾經是提效的非常簡單直接的方式。
靜態 API 實現有很多種方式,比如簡單的基于 Express / Koa 這樣的成熟框架,也可以采用專門的靜態 API 框架,比如著名的 typicode/json-server,想實現 REST API,你只需要編輯 db.json,放入你的數據即可。
{ "posts": [ { "id": 1, "title": "json-server", "author": "typicode" } ], "comments": [ { "id": 1, "body": "some comment", "postId": 1 } ], "profile": { "name": "typicode" } }
啟動服務器:
$ json-server --watch db.json
此時訪問網址 http://localhost:3000/posts/1,即我們剛才仿造的靜態 API 接口,返回數據如下:
{ "id": 1, "title": "json-server", "author": "typicode" }
還有更好的解決方案,比如 YApi ,它是一個可本地部署的、打通前后端及 QA 的、可視化的接口管理平臺:http://yapi.demo.qunar.com/
其實,圍繞 API 我們可以做非常多的事兒,比如根據 API 生成請求,對服務器進行反向壓測,甚至是 check 后端接口是否異常等。很明顯,這對前端來說是極其友好的。下面是我幾年前畫的圖,列出了我們能圍繞 API 做的事兒,至今也不算過時。
通過社區,我們可以了解到當下主流的 API 演進過程。
1.GitHub v3 的 restful api,經典 rest;
2. 微博 API,非常傳統的 json 約定方式;
3. 在 GitHub 的 v4 版本里,使用 GraphQL 來構建 API,這也是個趨勢。
GraphQL 目前看起來比較火,那 GitHub 使用 GraphQL 到底解決的是什么問題呢?
GraphQL 既是一種用于 API 的查詢語言也是一個滿足你數據查詢的運行時。
下面看一個最簡單的例子:
很明顯,這和靜態 API 模擬是一樣的流程。但 GraphQL 要更強大一些,它可以將這些模型和定義好的 API 和后端很好的集成。于是 GraphQL 就統一了靜態 API 模擬和和后端集成。
開發者要做的,只是約定模型和 API 查詢方法。前后端開發者都遵守一樣的模型開發約定,這樣就可以簡化溝通過程,讓開發更高效。
如上圖所示,GraphQL Server 前面部分,就是靜態 API 模擬。GraphQL Server 后面部分就是與各種數據源進行集成,無論是 API、數據還是微服務。是不是很強大?
下面我們總結一下 API 的演進過程。
傳統方式:Fe 模擬靜態 API,后端參照靜態 API 去實習 rpc 服務。
時髦的方式:有了 GraphQL 之后,直接在 GraphQL 上編寫模型,通過 GraphQL 提供靜態 API,省去了之前開發者自己模擬 API 的問題。有了 GraphQL 模型和查詢,使用 GraphQL 提供的后端集成方式,后端集成更簡單,于是 GraphQL 成了前后端解耦的橋梁。
集成使用的就是基于 Apollo 團隊的 GraphQL 全棧解決方案,從后端到前端提供了對應的 lib ,使得前后端開發者使用 GraphQL 更加的方便。
GraphQL 本身是好東西,和 Rest 一樣,我的擔心是落地不一定那么容易,畢竟接受約定和規范是很麻煩的一件事兒。可是不做,又怎么能進步呢?
2018 年,有一個出乎意料的一個實踐,就是在瀏覽器可以直接調用 grpc 服務。RPC 服務暴漏 HTTP 接口,這事兒 API 網關就可以做到。事實上,gRPC-Web 也是這樣做的。
如果只是簡單透傳,意義不大。大多數情況,我們還是要在 Node.js 端做服務聚合,繼而為不同端提供不一樣的 API。這是比較典型的 API Proxy 用法,當然也可以叫 BFF(backend for frontend)。
從前端角度看,渲染和 API 是兩大部分,API 部分前端自己做有兩點好處:
1. 前端更了解前端需求,尤其是根據 ui/ue 設計 API;
2. 讓后端更專注于服務,而非 API。需求變更,能不折騰后端就盡量不要去折騰后端。這也是應變的最好辦法。
構建具有 Node.js 特色的微服務,也主要從 API 和渲染兩部分著手為主。如果說能算得上創新的,那就是 API 和渲染如何無縫結合,讓前端開發有更好的效率和體驗。
盡管 Node.js 中間層可以將 RPC 服務聚合成 API,但前端還是前端,API 還是 API。那么如何能夠讓它們連接到一起呢?比較好的方式就是通過 SSR 進行同構開發。服務端創新有限,搞來搞去就是不斷的升 v8,提升性能,新東西不多。
今天我最頭疼的是,被 Vue/React/Angular 三大框架綁定,喜憂參半,既想用組件化和雙向綁定(或者說不得不用),又希望保留足夠的靈活性。大家都知道 SSR 因為事件 /timer 和過長的響應時間而無法有很高的 QPS(夠用,優化難),而且對 API 聚合處理也不是很爽。更尷尬的是 SSR 下做前后端分離難受,不做也難受,到底想讓人咋樣?
對于任何新技術都是一樣的,不上是等死,上了是找死。目前是在找死的路上努力的找一種更舒服的死法。
目前,我們主要采用 React 做 SSR 開發,上圖中的 5 個步驟都經歷過了(留到 QCon 廣州場分享),這里簡單介紹一下 React SSR。React 16 現在支持直接渲染到節點流。渲染到流可以減少你內容的第一個字節(TTFB)的時間,在文檔的下一部分生成之前,將文檔的開頭至結尾發送到瀏覽器。當內容從服務器流式傳輸時,瀏覽器將開始解析 HTML 文檔。渲染到流的另一個好處是能夠響應背壓。
實際上,這意味著如果網絡被備份并且不能接受更多的字節,那么渲染器會獲得信號并暫停渲染,直到堵塞清除。這意味著你的服務器會使用更少的內存,并更加適應 I / O 條件,這兩者都可以幫助你的服務器擁有具有挑戰性的條件。
在 Node.js 里,HTTP 是采用 Stream 實現的,React SSR 可以很好的和 Stream 結合。比如下面這個例子,分 3 步向瀏覽器進行響應。首先向瀏覽器寫入基本布局 HTML,然后寫入 React 組件<MyPage/>,然后寫入</div></body></html>。
// 服務器端 // using Express import { renderToNodeStream } from "react-dom/server" import MyPage from "./MyPage" app.get("/", (req, res)=> { res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>"); res.write("<div id='content'>"); const stream=renderToNodeStream(<MyPage/>); stream.pipe(res, { end: false }); stream.on('end', ()=> { res.write("</div></body></html>"); res.end(); }); });
這段代碼里需要注意stream.pipe(res, { end: false }),res 本身是 Stream,通過 pipe 和<MyPage/>返回的 stream 進行綁定,繼而達到 React 組件嵌入到 HTTP 流的目的。
上面是服務器端的做法,與此同時,你還需要在瀏覽器端完成組件綁定工作。react-dom 里有 2 個方法,分別是 render 和 hydrate。由于這里采用 renderToNodeStream,和 hydrate 結合使用會更好。當 MyPage 組件的 html 片段寫到瀏覽器里,你需要通過 hydrate 進行綁定,代碼如下。
// 瀏覽器端 import { hydrate } from "react-dom" import MyPage from "./MyPage" hydrate(<MyPage/>, document.getElementById("content"))
可是,如果有多個組件,需要寫入多次流呢?使用 renderToString 就簡單很多,普通模板的方式,流卻使得這種玩法變得很麻煩。
偽代碼:
const stream1=renderToNodeStream(<MyPage/>); const stream2=renderToNodeStream(<MyTab/>); res.write(stream1) res.write(stream2) res.end()
核心設計是先寫入布局,然后寫入核心模塊,然后再寫入其他模塊。
1) 布局 (大多數情況靜態 html 直接吐出,有可能會有請求);
2) Main(大多數情況有請求);
3) Others。
于是:
class MyComponent extends React.Component { fetch(){ // 獲取數據 } parse(){ // 解析,更新 state } render(){ ... } }
在調用組件渲染之前,先獲得 renderToNodeStream,然后執行 fetch 和 parse 方法,取到結果之后再將 Stream 寫入到瀏覽器。當前端接收到這個組件編譯后的 html 片段后,就可以根據 containerID 直接寫入,當然如果需要,你也可以根據服務器端傳過來的 data 進行定制。
前后端如何通信、服務端代碼如何打包、css 如何直接插入、和 eggjs 如何集成,這是目前我主要做的事兒。對于 API 端已經很成熟,對于 SSR 簡單的做法也是有的,比如 next.js 通過靜態方法 getInitialProps 完成接口請求,但只適用比較簡單的應用場景(一鍵切換 CSR 和 SSR,這點設計的確實是非常巧妙的)。但是如果想更靈活,處理更負責的項目,還是很有挑戰的,需要實現上面更為復雜的模塊抽象。在 2019 年,應該會補齊這塊,為構建具有 Node.js 特色的服務再拿下一塊高地。
簡單地說,Serverless=FAAS + BaaS ,服務如果被認為是 Serverless 的,它必須無需顯式地配置,并能自動調整擴縮容以及根據使用情況進行計費。云 function 是當今無 Serverless 計算中的通用元素,并引領著云的簡化和通用編程模型發展的方向。
2015 年亞馬遜推出了一項名為 AWS Lambda 服務的新選項。Node.js 領域 TJ 大神去創業,開發了 http://apex.run。目前,各大廠都在 Serverless 上發力,比如 Google、AWS、微軟,阿里云等。
這里不得不提一下 Eventloop,Node.js 成也 Eventloop,敗也 Eventloop,本身 Eventloop 是黑盒,開發將什么樣的代碼放進去你是很難全部覆蓋的,偶爾會出現 Eventloop 阻塞的情況,排查起來極為痛苦。
而利用 Serverless,可以有效的防止 Eventloop 阻塞。比如加密,加密是常見場景,但本身的執行效率非常慢。如果加解密和你的其他任務放到一起,很容易導致 Eventloop 阻塞。
如果加解密服務是獨立的服務呢?比如在 AWS 的 Lambda 上發布上面的代碼,它自身是獨立的,按需來動態擴容機器,可以去除 CPU 密集操作對 Node.js 的影響,快速響應流量變化。
這是趨勢,對于活動類的尤其劃算。你不知道什么時候是峰值,需要快速動態擴容能力,你也不會一直使用,按需付費更好。就算這個服務掛了,對其他業務也不會有什么影響,更不會出現阻塞 Eventloop 導致雪崩的情況。
在前端領域,Serverless 會越來越受歡迎,除了能完成 API Proxy,BFF 這種功能外,還可以減少前端運維成本,還是可以期望一下的。
2018 年有一個大家玩壞的梗:想提升性能,最簡單的辦法就是升級到最新 LTS 版本。因為 Node.js 依賴 v8 引擎,每次 v8 發版優化,新版 Node.js 集成新版 v8,于是性能就被提升了。
其他手段,比如使用 fast-json-stringify 加速 JSON 序列化,通過 Schema 知道每個字段的類型,那么就不需要遍歷、識別字段類型,而是可以直接用序列化對應的字段,這就大大減少了計算開銷,這就是 fast-json-stringfy 的原理,在某些情況下甚至可以比 JSON.stringify 快接近 10 倍左右。
在 2018 年,Node.js 非常穩定的前進著。下面看一下 Node.js 發版情況,2018-04-24 發布 Node.js v10,在 2018-10-23 發布 Node.js v11,穩步增長。下圖是 Node.js 的發布計劃。
可以看到,Node.js 非常穩定,API 也非常穩定,變化不大,一直緊跟 V8 升級的腳步,不斷的提升性能。在新版本里,能夠值得一說的,大概就只有 http2 的支持。
在 HTTP/2 里引入的新特性有:
目前,HTTP/2 已經開始落地,并且越來越穩定,高性能。HTTP/2 在 Node.js v8.4 里加入,在 Node.js v10 變為 Stable 狀態,大家可以放心使用。示例代碼如下。
const http2=require('http2'); const fs=require('fs'); const server=http2.createSecureServer({ key: fs.readFileSync('localhost-privkey.pem'), cert: fs.readFileSync('localhost-cert.pem') }); server.on('error', (err)=> console.error(err));
其他比如 trace_events,async_hooks 等改進都比較小。Node.js 10 將 npm 從 5.7 更新到 v6,并且在 node 10 里增強了 ESM Modules 支持,但還是不是很方便(官方正在實現新的模塊加載器),不過很多知名模塊已經慢慢支持 ESM 特性了,一般在 package.json 里增加如下代碼。
{ "jsnext:main": "index.mjs", }
另外異常處理,終于可以根據 code 來處理了。
try { // foo } catch (err) { if (err.code==='ERR_ASSERTION') { . . . } else { . . . } }
最后再提 2 個模塊:
node-clinic 性能調試神器
https://clinicjs.org
這是一個 Node.js 性能問題的診斷工具,可以生成 CPU、內存使用、事件循環(Event loop) 延時和活躍的句柄的相關數據折線圖。
Lowjs 使用 Node.js 去開發 IoT
https://www.lowjs.org/
Node-RED 構建 IoT 很久前就有了,這里介紹一下 Lowjs。Low.js 是 Node.js 的改造版本,可以對低端操作有更好的支持。它是基于內嵌的對內存要求更低的 js 引擎 DukTape。Low.js 僅需使用不到 2MB 的硬盤和 1.5MB 的內存。
Ry 把 Deno 用 Rust 重寫了之后,就再也沒有人說 Deno 是下一代 Node.js 了。其中的原因大家大概能夠想明白,別有用心的人吹水還是很可怕的。Deno 基于 ts 運用時環境,底層使用 Rust 編寫。性能、安全性上都很好,但舍棄了 npm 生態,需要做的事兒還是非常多的,甚至有人將 Koa 移植過去,也是蠻有意思的事兒。如果 Deno 真的能走另一條路,也是非常好的事兒。
不知道還有多少人還記得,Google 的 ChromeOS 的理念是“瀏覽器即操作系統”。現在看來,未來已經不遠了。通過各種研究,我們有理由堅定 Web 信仰,未來大前端的前景會更好,此時此刻,只是剛剛開始。
這里我再分享一些參加 Google IO 時了解到的信息:
為什么會產生這樣的改變?原因在于:
這里首先要感謝 Chrome+Android 的嘗試,使得 PWA 擁有和 Android 應用同等的待遇和權限。谷歌同時擁有 Chrome 和 Android,所以才能夠在上面做整合,進一步擴大 Web 開發的邊界。通過嘗試,開放,最終形成標準,乃至是業界生態。很明顯,作為流量入口,掌握底層設施能力是無比重要的。
Chrome 還提供了相應 Web 端的 API,如 web pay、web share、credential management api、media session 等。
Chrome 作為入口是可怕,再結合 Android,使得 Google 輕松完成技術創新,繼而形成標準規范,推動其他廠商,一直領先是可怕的。
前端的爆發,說來也就是最近 3、4 年的事情,其最根本的創造力根源在 Node.js 的助力。Node.js 讓更多人看到了前端的潛力,從服務器端開發,到各種腳手架、開發工具,前端開始沉浸在造輪子的世界里無法自拔。組件化后,比如 SSR、PWA 等輔助前端開發的快速開發實踐你幾乎躲不過去,再到 API 中間層、代理層到專業的后端開發都有非常成熟的經驗。
我親歷了從 Node 0.10 到 iojs,從 Node v4 到目前的 Node v11,寫了很多文章,參加過很多技術大會,也做過很多次演講,有機會和業內很多高手交流。當然,我也從 Qunar 到阿里,經歷了各種 Node 應用場景,對于 Node 的前景我是非常篤定的。善于使用 Node 有無數好處,想快速出成績,想性能調優,想優化團隊結構,想人員招聘,選擇 Node 是不會有錯的,諸多利好都讓我堅定的守護 Node.js,至少 5 年以上。
我想跟很多技術人強調的是,作為前端開發,你不能只會 Web 開發技術,你需要掌握 Node,你需要了解移動端開發方式,你需要對后端有更多了解。而擁有更多的 Node.js 和架構知識,能夠讓你如魚得水,開啟大前端更多的可能性。
如果前面有二輛車,一輛是保時捷一輛是眾泰,如果你必須撞一輛,你選哪個?
理性思維是哪個代價最低撞哪個,前提是你能夠判斷這兩輛車的價值,很明顯保時捷要比眾泰貴很多。講這個的目的是希望大家能夠理解全棧的好處。全棧是一種信仰,不是拿來吹牛逼的,而是真的可以解決更多問題,同時也能讓自己的知識體系不留空白,享受自我實現的極致快樂。另外,如果你需要了解更多的架構知識,全棧也是個不錯的選擇。
以我為例,我從接觸全棧概念到現在,經歷了以下四個階段:
不忘初心,堅持每天都能寫代碼,算是我最舒服自豪的事兒了吧,以前說越大越忙,現在要說越老越忙了,有了孩子,帶人,還想做點事兒,能安靜的寫會代碼其實不容易。
說了這么多,回到大前端話題,至少目前看 2019 年都是好事,一切都在趨于穩定和標準化,大家不必要過于焦慮。不過,掌握學習能力始終是最重要的,還是那兩句話:“廣積糧,高筑墻,緩稱王”,“少抱怨,多思考,未來更美好”。
做一個堅定的 Web 信仰者,把握趨勢,選擇比努力更重要!
推薦閱讀
2019年大前端技術趨勢深度解讀
作者簡介
狼叔(網名 i5ting),現為阿里巴巴前端技術專家,Node.js 技術布道者,Node 全棧公眾號運營者。曾就職于去哪兒、新浪、網秦,做過前端、后端、數據分析,是一名全棧技術的實踐者,目前主要關注技術架構和團隊梯隊建設方向。即將出版《狼書》3 卷。
最后給自己打一個廣告,今年 6 月 20 日北京舉辦的 GMTC 大會上,我會擔任 Node 專場出品人,主要關注 Serverless,TypeScript 在 Web 開發框架里相關實踐,以及性能,SSR,架構相關的 topic,如果你有想法,想分享的話,歡迎聯系我。
文地址:Top Node.js Frameworks to use in 2021
原文作者:Ronak Patel
Node.js 是最敏捷的服務端 web 應用平臺,因為它為應用開發公司提供了構建可擴展的單一編程語言 web 平臺的便利。它是最熱門的開源的 JavaScript 運行時框架之一,具有跨平臺屬性,讓我們可以在瀏覽器以外的環境運行代碼。
圖片來源:npmtrends
下面是一些關于 Node.js 的更多信息:
我們已經介紹了 Node.js,并詳細地了解了它的功能,現在我們可以討論 2021 年最值得使用的 Node.js 框架啦。
GitHub
NPM 周下載量:105,065
License:BSD-3-Clause
Hapi.js 框架流行度
Hapi.js 是眾多開發者信賴的最簡單、安全、可靠的框架之一。你可以使用 Hapi.js 來創建可擴展和健壯的應用程序,它具有最小的開銷和開箱即用的功能。它是開發 JSON API 的頂級Node.js框架。
Hapi.js 可以被用于:
Hapi.js 主要特性:
什么時候使用 Hapi.js:
Hapi.js 是開發安全、實時、可擴展和社交媒體應用的理想選擇。大多數移動應用開發者都喜歡用 Hapi.js 來創建代理和 API 服務器。
誰在使用 Hapi.js:
GitHub
NPM 周下載量:17,193,915
License:MIT
Express.js 流行度
Express.js 是一個靈活而簡約的 Node.js 應用框架。這個插件并不是圍繞著特定的組件構建的,因此它并不限制你使用什么技術。這就給了開發者嘗試的自由。他們還可以獲得閃電般的配置和純 JavaScript 體驗,這些特性使 Express.js 成為快速原型設計和敏捷開發市場的有力競爭者。
Express.js 可以被用于:
Express.js 主要特性:
什么時候使用 Express.js:
Express.js 是快速創建 Web 應用程序和服務的理想選擇,因為它有現成的 API 生成工具。它是基于 JavaScript 的全棧方案 MEAN 的一部分。這意味著你可以使用 Express.js 來制作任何基于瀏覽器的企業級應用。
誰在使用 Express.js:
Github
NPM 周下載量:508,214
License:MIT
Nest.js 流行度
Nest.js 是一個服務器端應用框架,它是為了解放開發者的生產力,讓他們的生活變得更輕松而打造的。開發者通常為了更好地組織和管理代碼而使用這個 Node.js 框架。
Nest.js 可以被用于:
Nest.js 主要特性
什么時候使用 Nest.js:
Nest.js 主要用于編寫具有可擴展、可測試和松散耦合特點的應用。它將 Node.js 的擴展潛力提高到了一個全新的水平。它提供了結構和靈活性的適當平衡,可以高效地管理你的大型項目的代碼,并且仍然有結構感可循。
誰在使用 Nest.js:
GitHub
NPM 周下載量:870,944
License:MIT
Koa.js 框架流行度
Koa.js 是一個開源的 Node web 框架,由 Express.js 原班人馬創建。通過 Koa,他們的目標是為 Web 應用和 API 創建一個更小、更有價值、更強大的平臺。它提供了多種高效的方法,以讓構建服務的過程更快速。
Koa.js 可以被用于:
Koa.js 主要特性:
什么時候使用 Koa.js:
Koa.js 最適合用于創建服務器、路由、處理響應和處理錯誤。
誰在使用 Koa.js:
GitHub
NPM 周下載量:3,617,636
License:MIT
socket.io 流行度
Socket.io 是用來在客戶端和服務器端之間創建實時雙向通信的框架。要做到這一點,客戶端需要在瀏覽器中安裝 Socket.io,服務器也要集成 Socket.io 包。這使得數據可以在數百萬種形式中共享。然而,最受歡迎的方法仍然是 JSON。
Socket.io 由以下兩個部分組成:
注意: Socket.io 還兼容許多其他語言,如 Java、C+、Swift、Dart、.Net 和 Python。
Socket.io 可以被用于:
Socket.io 主要特性:
什么時候使用 Socket.io:
Socket.io 是最好的基于事件的實時雙向通信工具之一。任何想要在應用中添加實時分析功能的人都應該使用它。Socket.io 對于實時游戲應用也很有用。在實時游戲中使用基本的 HTTP 或 HTTPS 協議是不可行的,因為這些文件很大,建立通信需要時間。在這里,我們使用體積更小的 socket 包,幾乎是實時地完成工作,以獲得更流暢和更好的體驗。
誰在使用 Socket.io:
Github
NPM:不可用
License:MIT
Meteor.js 是一個開源的全棧 JavaScript 平臺,JavaScript 根據意圖不同運行在不同的地方。JavaScript 運行在 Web 瀏覽器內部;然后 JavaScript 運行在 Node.js 容器內的 Meteor 服務器上,支持 HTML 片段、靜態資源和 CSS 規則。
Meteor.js 可以被用于:
Meteor.js 主要特性:
什么時候使用 Meteor.js:
Meteor.js 具有快速原型設計的能力,并能生成跨平臺(Android、iOS、Web)的代碼。它也是最直接的學習框架之一,因為它不遵循任何嚴格的結構規則。因此,Meteor.js 應該被任何希望以最少的學習曲線為多個平臺創建應用程序的初級或中級開發人員使用。
誰在使用 Meteor.js:
Github
周下載數:3808
License:MIT
adoni.js 流行度
Adonis.js 是一個 Node.js的 MVC 框架,可以運行在所有的操作系統上。它為編寫服務器端 Web 應用程序提供了一個穩定的生態系統,以讓開發者專注于業務需求,如最終確定選擇或排除哪個包。對于想要換個口味,正在嘗試 Node.js 框架的 Laravel 開發者來說,它是理想的選擇。Adonis.js為 Node.js 提供了與Laravel自然具有的相同的功能和能力。
Adonis.js 可以被用于:
Adonis.js 主要特性:
什么時候使用 Adonis.js:
如果你是一個正在尋找 MVC 工具的 Node.js開發者,Adonis.js 是你的首選 Node.js 框架。然而,如果你是一個 Laravel 開發者或任何其他移動應用框架開發者,你仍然可以給 Adonis.js 一個機會,甚至從 PHP 遷移到 Node.js也可以嘗試一下 Adonis.js。
誰在使用 Adonis.js:
Github
周下載數:20,457
License:MIT
sails.js 框架流行度
Sails.js 是又一個實時 Node.js MVC 框架。它基于 Express 構建,其 MVC 架構與 Ruby on Rails 相似。它與 Ruby on Rails 的不同之處在于,它提供了對更現代的、以數據為中心的 API 和 Web 應用開發風格的支持。
Sails.js 可以被用于:
Sails.js 主要特性:
什么時候使用 Sails.js:
任何想要一個模擬 MVC 模式的 Node.js框架(如 Laravel 和 Ruby on Rails)、想要實現現代應用架構,并構建以數據為中心的 API 和實時應用的開發者都應該在他們的下一個項目中使用 Sails.js。
誰在使用 Sails.js:
市場上有很多新的 Node.js框架。不同的 Node.js框架會在不同階段幫助你開發項目,并帶來很多價值和功能。合理利用這些框架,媽媽再也不用擔心你的應用開發啦。
愛能遮掩一切過錯。
當我們在訪問一個站點的時候,如果訪問的地址不存在(404),或服務器內部發生了錯誤(500),站點會展示出某個特定的頁面,比如:
那么如何在
Koa
中實現這種功能呢?其實,一個簡單的中間件即可實現,我們把它稱為http-error
。實現過程并不復雜,拆分為三步來看:第一步:確認需求
第二步:整理思路
第三步:代碼實現
<br/>
確認需求
打造一個事物前,需要先確認它具有什么特性,這就是需求。
<br/>
在這里,稍微整理下即可得到幾個基本需求:
在頁面請求出現
400
、500
類錯誤碼的時候,引導用戶至錯誤頁面;提供默認錯誤頁面;
允許使用者自定義錯誤頁面。
<br/>
整理思路
現在,從一個請求進入
Koa
開始說起:一個請求訪問
Koa
,出現了錯誤;該錯誤會被
http-error
中間件捕捉到;錯誤會被中間件的錯誤處理邏輯捕捉到,并進行處理;
錯誤處理邏輯根據錯誤碼狀態,調用渲染頁面邏輯;
渲染頁面邏輯渲染出對應的錯誤頁面。
可以看到,關鍵點就是捕捉錯誤,以及實現錯誤處理邏輯和渲染頁面邏輯。
<br/>
代碼實現
建立文件
基于教程目錄結構,我們創建
middleware/mi-http-error/index.js
文件,存放中間件的邏輯代碼。初始目錄結構如下:middleware/ ├─ mi-http-error/ │ └── index.js └─ index.js
注意: 目錄結構不存在,需要自己創建。
<br/>
捕捉錯誤
該中間件第一項需要實現的功能是捕捉到所有的
http
錯誤。根據中間件的洋蔥模型,需要做幾件事:1. 引入中間件
修改
middleware/index.js
,引入mi-http-error
中間件,并將它放到洋蔥模型的最外層const path=require('path')const ip=require("ip")const bodyParser=require('koa-bodyparser')const nunjucks=require('koa-nunjucks-2')const staticFiles=require('koa-static')const miSend=require('./mi-send')const miLog=require('./mi-log')// 引入請求錯誤中間件const miHttpError=require('./mi-http-error')module.exports=(app)=> { // 應用請求錯誤中間件 app.use(miHttpError()) app.use(miLog(app.env, { env: app.env, projectName: 'koa2-tutorial', appLogLevel: 'debug', dir: 'logs', serverIp: ip.address() })); app.use(staticFiles(path.resolve(__dirname, "../public"))) app.use(nunjucks({ ext: 'html', path: path.join(__dirname, '../views'), nunjucksConfig: { trimBlocks: true } })); app.use(bodyParser()) app.use(miSend()) }
2. 捕獲中間件異常情況
修改
mi-http-error/index.js
,在中間件內部對內層的其它中間件進行錯誤監聽,并對捕獲catch
到的錯誤進行處理module.exports=()=> { return async (ctx, next)=> { try { await next(); /** * 如果沒有更改過 response 的 status,則 koa 默認的 status 是 404 */ if (ctx.response.status===404 && !ctx.response.body) ctx.throw(404); } catch (e) { /*此處進行錯誤處理,下面會講解具體實現*/ } } }
<br/>
上面的準備工作做完,下面實現兩個關鍵邏輯。
<br/>
錯誤處理邏輯
錯誤處理邏輯其實很簡單,就是對錯誤碼進行判斷,并指定要渲染的文件名。這段代碼運行在錯誤
catch
中。修改
mi-http-error/index.js
:module.exports=()=> { let fileName='other' return async (ctx, next)=> { try { await next(); /** * 如果沒有更改過 response 的 status,則 koa 默認的 status 是 404 */ if (ctx.response.status===404 && !ctx.response.body) ctx.throw(404); } catch (e) { let status=parseInt(e.status) // 默認錯誤信息為 error 對象上攜帶的 message const message=e.message // 對 status 進行處理,指定錯誤頁面文件名 if(status >=400){ switch(status){ case 400: case 404: case 500: fileName=status; break; // 其它錯誤 指定渲染 other 文件 default: fileName='other' } } } } }
也就是說,對于不同的情況,會展示不同的錯誤頁面:
├─ 400.html├─ 404.html├─ 500.html├─ other.html
這幾個頁面文件我們會在后面創建,接下來我們開始講述下頁面渲染的問題。
<br/>
渲染頁面邏輯
首先我們創建默認的錯誤頁面模板文件
mi-http-error/error.html
,這里采用nunjucks
語法。<!DOCTYPE html><html><head> <title>Error - {{ status }}</title></head><body> <div id="error"> <h1>Error - {{ status }}</h1> <p>Looks like something broke!</p> {% if (env==='development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div></body></html>
<br/>
因為牽涉到文件路徑的解析,我們需要引入
path
模塊。另外,還需要引入nunjucks
工具來解析模板。path
是node
模塊,我們只需從npm
上安裝nunjucks
即可。<br/>
安裝
nunjucks
模塊來解析模板文件:npm i nunjucks -S
<br/>
修改
mi-http-error/index.js
,引入path
和nunjucks
模塊:// 引入 path nunjucks 模塊 const Path=require('path') const nunjucks=require('nunjucks')module.exports=()=> { // 此處代碼省略,與之前一樣}
<br/>
為了支持自定義錯誤文件目錄,原來調用中間件的代碼需要修改一下。我們給中間件傳入一個配置對象,該對象中有一個字段
errorPageFolder
,表示自定義錯誤文件目錄。修改
middleware/index.js
:// app.use(miHttpError())app.use(miHttpError({ errorPageFolder: path.resolve(__dirname, '../errorPage') }))
注意: 代碼中,我們指定了
/errorPage
為默認的模板文件目錄。<br/>
修改
mi-http-error/index.js
,處理接收到的參數:const Path=require('path') const nunjucks=require('nunjucks')module.exports=(opts={})=> { // 400.html 404.html other.html 的存放位置 const folder=opts.errorPageFolder // 指定默認模板文件 const templatePath=Path.resolve(__dirname, './error.html') let fileName='other' return async (ctx, next)=> { try { await next() if (ctx.response.status===404 && !ctx.response.body) ctx.throw(404); } catch (e) { let status=parseInt(e.status) const message=e.message if(status >=400){ switch(status){ case 400: case 404: case 500: fileName=status; break; default: fileName='other' } }else{// 其它情況,統一返回為 500 status=500 fileName=status } // 確定最終的 filePath 路徑 const filePath=folder ? Path.join(folder, `${fileName}.html`) : templatePath } } }
<br/>
路徑和參數準備好之后,我們需要做的事情就剩返回渲染的頁面了。
<br/>
修改
mi-http-error/index.js
,對捕捉到的不同錯誤返回相應的視圖頁面:const Path=require('path') const nunjucks=require('nunjucks')module.exports=(opts={})=> { // 增加環境變量,用來傳入到視圖中,方便調試 const env=opts.env || process.env.NODE_ENV || 'development' const folder=opts.errorPageFolder const templatePath=Path.resolve(__dirname, './error.html') let fileName='other' return async (ctx, next)=> { try { await next() if (ctx.response.status===404 && !ctx.response.body) ctx.throw(404); } catch (e) { let status=parseInt(e.status) const message=e.message if(status >=400){ switch(status){ case 400: case 404: case 500: fileName=status; break; default: fileName='other' } }else{ status=500 fileName=status } const filePath=folder ? Path.join(folder, `${fileName}.html`) : templatePath // 渲染對應錯誤類型的視圖,并傳入參數對象 try{ // 指定視圖目錄 nunjucks.configure( folder ? folder : __dirname ) const data=await nunjucks.render(filePath, { env: env, // 指定當前環境參數 status: e.status || e.message, // 如果錯誤信息中沒有 status,就顯示為 message error: e.message, // 錯誤信息 stack: e.stack // 錯誤的堆棧信息 }) // 賦值給響應體 ctx.status=status ctx.body=data }catch(e){ // 如果中間件存在錯誤異常,直接拋出信息,由其他中間件處理 ctx.throw(500, `錯誤頁渲染失敗:${e.message}`) } } } }
上面所做的是使用渲染引擎對模板文件進行渲染,并將生成的內容放到
Http
的Response
中,展示在用戶面前。感興趣的同學可以去中間件源碼中查看error.html
查看模板內容(其實是從koa-error
那里拿來稍作修改的)。<br/>
在代碼的最后,我們還有一個異常的拋出
ctx.throw()
,也就是說,中間件處理時候也會存在異常,所以我們需要在最外層做一個錯誤監聽處理。修改
middleware/index.js
:const path=require('path')const ip=require("ip")const bodyParser=require('koa-bodyparser')const nunjucks=require('koa-nunjucks-2')const staticFiles=require('koa-static')const miSend=require('./mi-send')const miLog=require('./mi-log')const miHttpError=require('./mi-http-error')module.exports=(app)=> { app.use(miHttpError({ errorPageFolder: path.resolve(__dirname, '../errorPage') })) app.use(miLog(app.env, { env: app.env, projectName: 'koa2-tutorial', appLogLevel: 'debug', dir: 'logs', serverIp: ip.address() })); app.use(staticFiles(path.resolve(__dirname, "../public"))) app.use(nunjucks({ ext: 'html', path: path.join(__dirname, '../views'), nunjucksConfig: { trimBlocks: true } })); app.use(bodyParser()) app.use(miSend()) // 增加錯誤的監聽處理 app.on("error", (err, ctx)=> { if (ctx && !ctx.headerSent && ctx.status < 500) { ctx.status=500 } if (ctx && ctx.log && ctx.log.error) { if (!ctx.state.logged) { ctx.log.error(err.stack) } } }) }
<br/>
下面,我們增加對應的錯誤渲染頁面:
創建
errorPage/400.html
:<!DOCTYPE html><html><head> <title>400</title></head><body> <div id="error"> <h1>Error - {{ status }}</h1> <p>錯誤碼 400 的描述信息</p> {% if (env==='development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div></body></html>
<br/>
創建
errorPage/404.html
:<!DOCTYPE html><html><head> <title>404</title></head><body> <div id="error"> <h1>Error - {{ status }}</h1> <p>錯誤碼 404 的描述信息</p> {% if (env==='development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div></body></html>
<br/>
創建
errorPage/500.html
:<!DOCTYPE html><html><head> <title>500</title></head><body> <div id="error"> <h1>Error - {{ status }}</h1> <p>錯誤碼 500 的描述信息</p> {% if (env==='development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div></body></html>
<br/>
創建
errorPage/other.html
:<!DOCTYPE html><html><head> <title>未知異常</title></head><body> <div id="error"> <h1>Error - {{ status }}</h1> <p>未知異常</p> {% if (env==='development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div></body></html>
<br/>
errorPage
中的頁面展示內容,可以根據自己的項目信息修改,以上僅供參考。<br/>
至此,我們基本完成了用來處理『請求錯誤』的中間件。而這個中間件并不是固定的形態,大家在真實項目中,還需要多考慮自己的業務場景和需求,打造出適合自己項目的中間件。
下一節中,我們將學習下規范與部署——制定合適的團隊規范,提升開發效率。
上一篇:iKcamp新課程推出啦~~~~~iKcamp|基于Koa2搭建Node.js實戰(含視頻)? 處理靜態資源
推薦: 翻譯項目Master的自述:
1. 干貨|人人都是翻譯項目的Master
2. iKcamp出品微信小程序教學共5章16小節匯總(含視頻)
*請認真填寫需求信息,我們會在24小時內與您取得聯系。