者|狼叔(阿里前端技术专家,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小时内与您取得联系。