微信扫一扫
分享到朋友圈

2019年Node趋势解读:大前端如何与Node结合?

作者:InfoQ 来源:InfoQ 公众号
分享到:

04-09

作者 | 狼叔
编辑 | 覃云

你好,我是阿里巴巴前端技术专家狼叔,在  中,我分享了大前端的现状和未来,接下来的这篇文章,我将会分享一些大前端跟 Node.js 结合比较密切的点。

本文首发于极客时间《技术领导力 300 讲》专栏:

https://time.geekbang.org/column/intro/79?utm_source=frontshow&utm_medium=sitenavigation

1 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 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 一样,我的担心是落地不一定那么容易,毕竟接受约定和规范是很麻烦的一件事儿。可是不做,又怎么能进步呢?

3 构建具有 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 和返回的 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 特色的服务再拿下一块高地。

4 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 这种功能外,还可以减少前端运维成本,还是可以期望一下的。

5 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 的内存。

6 关于 Deno

Ry 把 Deno 用 Rust 重写了之后,就再也没有人说 Deno 是下一代 Node.js 了。其中的原因大家大概能够想明白,别有用心的人吹水还是很可怕的。Deno 基于 ts 运用时环境,底层使用 Rust 编写。性能、安全性上都很好,但舍弃了 npm 生态,需要做的事儿还是非常多的,甚至有人将 Koa 移植过去,也是蛮有意思的事儿。如果 Deno 真的能走另一条路,也是非常好的事儿。

7 未来已来

不知道还有多少人还记得,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 信仰者,把握趋势,选择比努力更重要!

作者介绍

狼叔(网名 i5ting),现为阿里巴巴前端技术专家,Node.js 技术布道者,Node 全栈公众号运营者。曾就职于去哪儿、新浪、网秦,做过前端、后端、数据分析,是一名全栈技术的实践者,目前主要关注技术架构和团队梯队建设方向。即将出版《狼书》3 卷。

最后给自己打一个广告,今年 6 月 20 日北京举办的 GMTC 大会上,我会担任 Node 专场出品人,主要关注 Serverless,TypeScript 在 Web 开发框架里相关实践,以及性能,SSR,架构相关的 topic,如果你有想法,想分享的话,欢迎联系我。

欢迎扫以下二维码或 戳阅读原文 了解详情。

今日荐文

点击下方图片即可阅读

996.ICU,中国程序员的呐喊与彷徨



点个在看少个 bug

阅读38079
如何 
举报0
关注InfoQ微信号:infoqchina

用微信扫描二维码即可关注
声明

1、头条易读遵循行业规范,任何转载的稿件都会明确标注作者和来源;
2、本文内容来自“InfoQ”微信公众号,文章版权归InfoQ公众号所有。

评论
更多

文章来自于公众号:

InfoQ

微信号:infoqchina

邮箱qunxueyuan#163.com(将#换成@)
微信编辑器
免责声明
www.weixinyidu.com   免责声明
版权声明:本站收录微信公众号和微信文章内容全部来自于网络,仅供个人学习、研究或者欣赏使用。版权归原作者所有。禁止一切商业用途。其中内容并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现头条易读网站上有侵犯您的知识产权的内容,请与我们联系,我们会及时修改或删除。
本站声明:本站与腾讯微信、微信公众平台无任何关联,非腾讯微信官方网站。