2019年Node趋势解读:大前端如何与Node结合?
作者|狼叔(阿里前端技术专家,Node技术布道者)
编辑|覃云
你好,我是阿里巴巴前端技术专家狼叔,在上一篇文章中,我分享了大前端的现状和未来,接下来的这篇文章,我将会分享一些大前端跟 Node.js 结合比较密切的点。
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 的一点看法。
API 演进与看起来较火的 GraphQL
书本上的软件工程在互联网高速发展的今天已经不那么适用了,尤其是移动开发火起来之后,所有企业都崇尚敏捷开发,快鱼吃慢鱼,甚至觉得 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 一样,我的担心是落地不一定那么容易,毕竟接受约定和规范是很麻烦的一件事儿。可是不做,又怎么能进步呢?
构建具有 Node.js 特色的服务
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
简单地说,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 导致雪崩的情况。
- 可靠性:99.999999999%
- 可用性:99.99%
- 无限存储空间
- 按量付费
在前端领域,Serverless 会越来越受欢迎,除了能完成 API Proxy,BFF 这种功能外,还可以减少前端运维成本,还是可以期望一下的。
Node.js 新特性
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 里引入的新特性有:
- Multiplexing 多路复用
- Single Connection 每个源一个连接
- Server Push 服务器端推送
- Prioritization 请求优先级
- Header Compression 头部压缩
目前,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 的内存。
关于 Deno
Ry 把 Deno 用 Rust 重写了之后,就再也没有人说 Deno 是下一代 Node.js 了。其中的原因大家大概能够想明白,别有用心的人吹水还是很可怕的。Deno 基于 ts 运用时环境,底层使用 Rust 编写。性能、安全性上都很好,但舍弃了 npm 生态,需要做的事儿还是非常多的,甚至有人将 Koa 移植过去,也是蛮有意思的事儿。如果 Deno 真的能走另一条路,也是非常好的事儿。
未来已来
不知道还有多少人还记得,Google 的 ChromeOS 的理念是“浏览器即操作系统”。现在看来,未来已经不远了。通过各种研究,我们有理由坚定 Web 信仰,未来大前端的前景会更好,此时此刻,只是刚刚开始。
这里我再分享一些参加 Google IO 时了解到的信息:
- PWA 证明了浏览器的缓存能力;
- 投屏、画中画、push 等原生应用有的功能也都支持了;
- Web Components 标准化;
- 编解码新方案 av1,效率有极大的提升。
为什么会产生这样的改变?原因在于:
- Web 开发主流化,无论移动端还是 PC 端,都能够复用前端技能,又能跨平台,这是 ROI 最高的方式。
- Node 和 Chrome 一起孕育出了 Electron/Nw.js 这样的打包加壳工具,打通了前端和 Native API 的通道,让 WebView 真正的跨平台。
- PWA 对于缓存的增加,以及推送、安装过程等抽象,使得 Web 应用拥有了可以媲美 native client 的能力。
这里首先要感谢 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,如果你有想法,想分享的话,欢迎联系我。