前端同构渲染的思考与实践-程序员宅基地

技术标签: ViewUI  ui  webpack  前端  

开篇

前端同构渲染的相关架构,给我最直观的感受,这是前端渲染最为复杂的一种方案,也是为了追求极致的用户体验不得不去做的一种尝试,虽然 Node.js 的引入赋能了传统前端领域、SEO 优化也不再是个问题,但很明显,这些只是副产品。

问题

上帝为了我们开了一扇窗,同时也会为我们关上一扇门。

我们所知的传统型 SPA,单页面应用,贴近用户端越近,交互越复杂,它的弊端就越明显,在我们享受 JavaScirpt 给我们带来的无刷新体验和组件化带来的开发效率的同时,『白屏』这个随着 SPA 各种优点随之而来的缺点被遗忘,我们拥有菊花方案在 JavaScript 没有将 DOM 构建好之前蒙层,拥有白屏监控方案将真实用户数据上报改进,但并没有触碰到白屏问题的本质,那就是『DOM 的构建者是 JavaScript,而非原生的浏览器』。

<html>
  <head><title /></head>
  <body>
  	<div id="root"></div>
    <script src="render.js"></script>
  </body>
</html>
复制代码

如上代码,在 SPA 架构中,服务器端直接给出形如这样的 HTML,浏览器在渲染 body#root 这个节点完成之后,页面的绘制区域其实还是空的,直到 render.js 构建好真实的 DOM 结构之后再 append 到 #root上去。此时,首屏展示出来时,必然是 render.js 通过网络请求完毕,然后加上 JavaScript 执行完成之后的。

让我们回到最初的那个前端时代,那时候 JavaScript 还没有那么强大,我们的服务器端全部吐出 HTML 给前端,我们使用 jQuery 解决用户的交互,这种方式虽有很多弊病,但不可否认的是拥有理论上最低白屏时间。

<html>
	<head><title /></head>
  <body>
  	<div id="root">
    	<div class="header">
        <img src="logo.png" />
      </div>
      <div calss="content">
        <div class="shopitem">
        </div>
      </div>
    </div>
  </body>
</html>
复制代码

如上代码,在直出的服务器渲染中,浏览器直接拿到最终的 HTML,浏览器通过解析 HTML 之后将 DOM 元素生成而进行渲染。所以相比于 SPA,服务器端渲染从直观上看:

  • 转化 HTML 到 DOM,浏览器原生会比 JavaScript 生成 DOM 的时间短
  • 省去了 SPA 中 JavaScript 的请求与编译时间

**

解决

Node.js 的出现极大程度的给传统前端赋予了更大的能量,前端的分离也从前期的物理文件的区分转变为职责上的区分,前端开发者从页面仔的噩梦中解脱出来,最重要的是,JavaScript 能在服务器端执行了。在享受这些红利的同时,我们就会不自觉的设想一种方案,它拥有 SPA 的大部分优点,却解决了它大部分的缺点,那就是服务器端输出 HTML,然后由客户端复用该 HTML,继续 SPA 模式,这样岂不是既解决了白屏和 SEO 问题,又继承了无刷新的用户体验和开发的组件化嘛。

嗯,如果这样的话,就会有个一致性的问题。我们必须在浏览器端复用服务器端输出的 HTML 才能避免多套代码的适配,而传统的模板渲染是可行的,只要选择一套同时支持浏览器和 Node.js 的模板引擎就能搞定。我们写好模板, 在 Node.js 准备好数据,然后将数据灌入模板产出 HTML,输出到浏览器之后由客户端 JavaScript 承载交互,搞定。

软件开发中遇到的所有问题,都可以通过增加一层抽象而得以解决

思路到了这里,我们就会发现,『模板』其实是一种抽象层,虽然底层的 HTML 只能跑在浏览器端,但是顶层的模板却能通过模板引擎同时跑在浏览器和服务器端,此为垂直方向,在水平方向上,模板将数据和结构解耦,将数据灌入结构,这种灌入,实际是一锤子买卖,管生不管养。

随着时间的推进,组件化的大潮来了,其核心概念 Virtual DOM 依其声明式和高性能让前端开发者大呼爽爽爽,但究其本质,就是为了解决频繁操作 DOM 而在 HTML 之上做的一层抽象,与模板不同的是,它将数据与结构产生交互,有代表的要数 Facebook 方使用的单项数据流和 Vue 方使用的 MVVM 数据流,大道至简,我们观察函数 UI = F(data), 其中 UI 为最终产出前端界面,data 为数据,F 则为模板结构或者 Virtual DOM,模板的方式是 F 只执行一遍,而组件方式则为每次 data 改变都会再执行一遍

所以理论上,无论是模板方式还是组件方式,前后端同构的方案都呼之欲出,我们在 Node.js 端获取数据 ,执行 F 函数,得到 HTML输出给浏览器,浏览器 JavaScript 复用 HTML,继续执行 F 函数,等到数据变化,继续执行 F 函数,交互也得到解决,完美~~~

实施

但由于组件化大势所趋,下文将略去模板方案,我们以 Vue 为类比,下图表明其实施思路:

通用代码

由于 F 同时需要在浏览器端和服务器端执行,所以对于整个 Vue App,我们需要同时支持两端,也就是通用代码。所以我们需要将 SPA 架构的代码进行改造:

  • 分为两个入口,分为服务端和客户端,只引入通用代码,然后在不同的环境里调用各自的渲染函数。当然,在客户端 ReactDOM.render 会生成 DOM 结构,而服务器端通过 ReactServer.renderToString 将生成 HTML,需要由 HTTP Server 推给前端,各入口处解决特异的环境问题;
  • 通用代码中不可在不判定执行环境的情况下引用 DOM、调用 window、document 这些浏览器特异和引用 global process 这些服务器端特异的操作,这往往是引起 Node.js 服务出问题的根本原因;
  • 为了兼容两端,在选择库时,需要也同时需要支持两端,比如 axios,lodash 等;
  • React 和 Vue 都有生命周期,需要区分哪些生命周期是在浏览器中运行,哪些会在服务器端运行,或者是同时运行,如使用 Redux 或者 Vuex 等库,最好在组件上引入 asyncData 钩子进行数据请求,同时供两端使用;
  • 判定不同的执行环境可以通过注入 process.env.EXEC_ENV 来解决,形如:
if (process.env.EXEC_ENV === 'client') {
  window.addEventListener(...);
}

if (process.env.EXEC_ENV === 'server') {
}
复制代码

构建与运行

  • 在使用 webpack 进行构建时,需要将公共 App 部分打包出来,形成公共代码,由服务器端引入执行,而客户端可以引用打包好的公共代码,再用 webpack 引入之后进行特异处理即可;
  • 需要引入 Node.js 中间层,负责请求数据,提供渲染能力,提供 HTTP 服务,由于 HTML 模板需要在服务端引入,CDN 文件需要自行处理;
  • 至于 babel 的使用,可以在浏览器中通用处理,服务端只解决特殊语法,如 jsx,vue template;

新世界

至此,白屏问题问题看起来是解决了,通过把 JavaScript 的渲染逻辑放到 Node.js 端进行,我们加快了首屏出现的时间,但是联想到 Node.js 对前端的赋能,我们或许可以做的更多。

再议首屏

让我们把视角移动的更细致一些,关注『从服务器端输出 HTML』这一部分,其隐藏的含义是我们需要把 App 渲染的所有 HTML 都输出给前端,其实不然,举个栗子:

比如在移动端有一个页面,它有大约 10 屏的高度,如果我们在服务器端全部输出 10 屏其实是有点浪费的,我们可以只输出首屏需要的,从而降低 render 执行时间从而降低 TTFB 时间,让页面更快的到达用户眼前。实践中,一般情况是输出大概快两屏的样子,就能处理所以机型的高度问题,剩下的 8 屏,在浏览器端继续渲染,渐进产出内容,用户无感知。

资源控制

得益于 Node.js 输出 HTML 的另一层含义,就是我们可以直接在首次接触就能感知到客户端,也就有了足够的灵活性,再举个栗子:

有个针对安卓平台和 iOS 平台不同的脚本只要加载,如果在 SPA 情况下,只有等 JavaScript 执行时我们判定 navigation.userAgent 来获知先在是哪个平台,然后在 appendChild 一个 script 到 body,但如果服务端能首次接触就能感知,我们可以在服务端直接拿到 HTTP 请求中的 userAgent 判定平台,根据标识在模板中处理,很显然,这样很稳。

另外,如果有一些特别复杂的计算,服务端可以有更多的办法将数据更快的处理,以避免繁忙无比的浏览器接手。

缓存控制

一般的业务场景下,我们需要在 Node.js 中通过内网将数据获取到,然后通过 render 函数渲染出 HTML(一般需要将数据附带给 HTML 输出以便重复利用),这个时候我们可以通过页面访问地址和生成的 HTML 字符串做缓存策略,在缓存(一般选择 redis 等方案)之后,下次直接将同样的页面直接输出到前端,可大幅提高渲染性能

但这种方案也有很多限制,因为要考虑页面地址、多平台下、账户是否登录,页面是否需要改动等情况:

  • 页面地址纬度,在不同的地址下,HTML 输出不一致,所以 URL 可作为 key 的元素之一;
  • 未登录态,页面可以直接缓存,如需判定平台特异,需在 Node.js 端进行处理;
  • 已登录态,如果已缓存某一个已登录用户的 HTML,需要将跟登录相关的组件抹去重新换掉,或者直接给予未登录态页面,在客户端进行变更。

挑战

同构渲染看似美好,但其相对传统 SPA 确有着更多挑战:

Node.js

服务器端渲染相对应传统的 Node.js 应用,renderToString 函数不仅 CPU 密集,而且不同的组件对机器资源的要求不尽相同,这就更需要 Node.js 指标的监控、日志的记录、错误的收集、崩溃机制的完善。这里额外的关键的指标是 renderToString 的时间,它反应了 Node.js 渲染所使用的时间,如果加入缓存机制,就需要统计命中率等等。

代码质量

关于写通用代码,要求比 SPA 架构对开发者提出了更高的要求,我们需要小心再小心,因为万一搞错,将导致很难排查的内存泄露和 CPU 飙升,并且一旦出了问题,就像要修理天上跑的飞机一样,非常困难。还记得有一次在类似 componentWillMount 写了一些跟浏览器相关的代码导致的内存飙升,还有一次 JSON.stringify 一个大对象导致的 CPU 飙升,不堪回首。这方面 alinode 做的很好,确实可以满足这种飞机场景。

结语

为了效率, 前端们付出了艰辛的努力,无论是工程上我们千方百计的制造工具,还是组件化的引入,我们解决的是开发的效率,而无论是 Virtual DOM 的引入解决频繁操作的 DOM,还是用了提升用户体验而使用的 SPA 架构,我们解决的是用户的使用效率,是前端的性能。而同构渲染也是这样一种方案,它引入了 Node.js 的复杂度,要求我们写出限制更多的代码,其根本目的还是为了让用户更快更早的看到页面,那怕是 50 毫秒,那怕是 10 毫秒。


关于我们

我们是蚂蚁保险体验技术团队,来自蚂蚁金服保险事业群。我们是一个年轻的团队(没有历史技术栈包袱),目前平均年龄92年(去除一个最高分8x年-团队leader,去除一个最低分97年-实习小老弟)。我们支持了阿里集团几乎所有的保险业务。18年我们产出的相互宝轰动保险界,19年我们更有多个重量级项目筹备动员中。现伴随着事业群的高速发展,团队也在迅速扩张,欢迎各位前端高手加入我们~

我们希望你是:技术上基础扎实、某领域深入(Node/互动营销/数据可视化等);学习上善于沉淀、持续学习;性格上乐观开朗、活泼外向。

如有兴趣加入我们,欢迎发送简历至邮箱:[email protected]


本文作者:蚂蚁保险-体验技术组-月影

掘金地址:杨柳岸酱

转载于:https://juejin.im/post/5c821dc45188257e1f2915b1

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33877885/article/details/91440261

智能推荐

越南使用的越南文unicode编码范围_越南语unicode编码范围-程序员宅基地

文章浏览阅读1.9k次。记录:BasicLatin(U+0000-007F)LatinExtended-A(U+0100-017F)LatinExtendedAdditional(U+1E00-1EFF)CombiningDiacriticalMarks(U+0300-036F)CombiningDiacriticalMarksSupplement(U+1DC0-1DFF)CombiningDiacriticalMarksforSymbols(U+20D0-20FF)..._越南语unicode编码范围

RabbitMQ四种工作模式_rabbit 纯注解模式-程序员宅基地

文章浏览阅读251次。RabbitMQ四种工作模式创建队列public final static String USER_LOG = "user.log";@Beanpublic Queue userLogQueue() { /* durable:队列是否可持久化,默认为true。 exclusive: 队列是否具有排它性,默认为false。 autoDelete:队列没有任何订阅的消费者时是否自动删除,默认为false。 */ retur_rabbit 纯注解模式

种子轮、天使轮、A、B、C轮、IPO投资是什么意思及特点-程序员宅基地

文章浏览阅读3k次。其实,每一个轮次与公司发展的阶段紧密相关:种子:没有团队,只有想法,1-2个创始人,要一笔钱。投资额100-300万人民币不等,帮助公司启动。公司估值不会超过1500万人民币。天使:团队基本有了,大概3-5人,做了一段时间,产品马上要Demo了,可以内部看到。投资额300-600万,估值1500-3000万人民币左右。Pre-A:A轮前最后一轮,产品已经上线,但是还没有太多数据表现,需...

对于一个新手搭建个人博客会碰见的问题_jintaocms-程序员宅基地

文章浏览阅读255次。搭建这个博客之前我使用了好几个CMS建站程序,为什么我最终选择了使用WordPress建站?WordPress相比Z-Blog、typecho、Emlog(收费)能使用的模板和插件多、功能全、搭建更快(傻瓜式建站)。每个CMS建站程序都有各自的好处、优点。搭建个人博客我们需要用到(域名、空间)域名建议去知名平台购买,比如:万网(阿里)、GoDaddy、新网。域名对于一个网站非常重要,比..._jintaocms

【撸码caffe 五】数据层搭建-程序员宅基地

文章浏览阅读551次。caffe.cpp中的train函数内声明了一个类型为Solver类的智能指针solver:// Train / Finetune a model.int train() {…… shared_ptr > solver(caffe::SolverRegistry::CreateSolver(solver_param));…… }之后调用Solver

ARM汇编详解-程序员宅基地

文章浏览阅读1.7k次。基本指令学习在博文:keil下ARM汇编程序建立与调试简介中学习建立ARM汇编程序工程。本博文开始学习一步一步写ARM汇编程序。一、重要概念理解1. 立即数1)把数据转换成二进制形式,从低到高写成 4位1组的形式,最高位一组不够4位的前面补02)数1的个数,如果大于8个【可能也是立即数,取反】不是立即数,如果小于等于8个 进行下面步骤3)如果数据中间有连续的大于等..._arm汇编

随便推点

RAD Studio 下载地址(已经更新到12)-程序员宅基地

文章浏览阅读2.7k次,点赞2次,收藏8次。RAD Studio 下载地址(已经更新到11.3)

架构设计 接口幂等性原则,防重复提交Token管理_接口幂等性开发原则-程序员宅基地

文章浏览阅读243次。一、幂等性概念1、幂等简介编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会产生同样的作用影响。2、HTTP请求遵循Http协议的请求,越来越强调Rest请求风格,可以更好的规范和理解接口的设计。GET:用于获取资源,不应有副作用,所以是幂等的;POST:用于创建资源,重复提交POST请求可能产生两个不同的资源,有副作用不满足幂等性;PUT:用于更新操作,重复提交PUT请求只会对其URL中指定的资源有副作用,满足幂等性;_接口幂等性开发原则

产品读书《写给大家看的设计书》-设计-程序员宅基地

文章浏览阅读6k次,点赞2次,收藏4次。作者简介Robin Williams 世界著名的设计师、技术专家和畅销书作家。通过写书和授课,她已经影响了整整一代数字设计师,是Adobe和Mac技术社区内的偶像级专家,Publish Magazine、Adobe Magazine等杂志的专栏作家,是MacWorld Expo 等业界重要活动和组织的顾问委员会成员,还创办了Santa Fe 电影艺术学院。导言设计师网址导航..._写给大家看的设计书

如何让网站被百度快速收录,搜索引擎入站-程序员宅基地

文章浏览阅读1.1w次,点赞3次,收藏12次。面对百度算法的不断更新与完善,新站收录一直困扰着很多站长,那到底如何让百度快速收录呢?以下为几种方法,仅供参考,有待考证。(我的网站:http://www.lequyz.com 感谢大家来支持!!~~)一、百度收录原理分析百度和google收录的宣传手法大同小异,不用专门区别分开。新站百度收录时间为半个月的时间,15天是一个比较正常的平均时间。慢的话可能需要一个月或者是两个月的时间

阿里云云服务器上安装Apache_阿里云服务器配置apache服务浏览器访问什么地址-程序员宅基地

文章浏览阅读2.2k次。阿里云云服务器上安装Apache云服务器操作系统:CentOS客户端操作环境:windows操作步骤:运行 xshell 或者其他能连接即可安装 Apache 软件:yum install httpd设置 Apache 在服务器启动时运行:chkconfig –levels 235 httpd on在 Apache 配置文件中配置域名:vi /etc/httpd/conf/httpd.con_阿里云服务器配置apache服务浏览器访问什么地址

protel粘贴层丢失和网络表netlist丢失的问题_protel99复制黏贴pcbnet无连接-程序员宅基地

文章浏览阅读162次。1.先更新update到PCB2.然后特殊粘贴_protel99复制黏贴pcbnet无连接