首页 > 代码库 > 后端渲染实践——看掘金社区是如何实践的
后端渲染实践——看掘金社区是如何实践的
Vue.js、React.js 及 Angular.js 等等前端开发框架引入了 UI = framework(State) 的前端编程逻辑,大范围降低了前端业务开发的难度,尤其是面向复杂前端应用。而这些优质开源框架的普及、多端业务统一、前后端分离的需求让越来越多的架构设计将大部分业务逻辑写在了前端。
但是,纯前端产品也有着它的问题。上述的几个前端框架都支持了后端渲染的功能,从而融合了前后端的问题。如何有效地整合现有前端逻辑实现后端渲染、如何优化后端渲染性能、如何实现服务器流式吐内容更快地渲染页面的经验,会成为新一代 Web 开发的方向,提高前端业务开发的效率。在由七牛云主办的 ECUG 十周年盛会上,阴明为大家带来了他的实践分享。
阴明(掘金联合创始人、CEO)
前端框架的繁荣及成熟
从百家争鸣到三足鼎立
图 1
这是从网上找到的前端的状态(图 1 ),每一个颜色均是某一个前端库的分类。前端的世界就是如此,需要在一群的选项中选择一个,并且要跟其他的选项 PK 。
如图 1 所示,方框的部分写具体的业务代码,例如早期的 jQuery。Prototype 曾经完成了 2000 年内有复杂业务代码的前端,写了大量的页面,传统的后台 admin 等都是这样。再往上 Ember 比较适合业务稳定的系统使用,因为它一直坚持着向前兼容,它不像新的库,如果出了一个新版本基本上需要推倒重写;而 Backbone 是写比较复杂页面的一个库, Angular 、React 等等。
在这么繁杂的前端中,单纯写前端业务有很多选择。曾看到一个评论:“ 2016 年,你完成一个巨简单的业务,就需要 TypeScript 写代码,用 Fetch 发起异步请求,所有的代码编译程 ES6 ……”用了几十个库完成一个非常简单的问题。那么,在这样的前端生态下,它一定会是繁荣的,如果不繁荣,不会有很多人在这里做事情。
Web 技术和 JavaScript 到达各个领域
-
后端:Node.js 在业务开发中已经比较广泛使用,而且 v8 性能较好。
-
移动:最常用的 Hybrid ,React Native ,NativeScript ,Weex 。
-
桌面:Electron,nw.js 来实现 Web 端的应用,其实都是网页。
-
VR:WebVR ,A-Frame ,WebGL
-
硬件:Cylon.js ,Tessel ,Johnny-Five
-
数据可视化:d3.js ,vis.js ,HighCharts ,Charts
因为 JavaScript 本身的代码,学习陡峭程度非常低,入门门槛低,并且网页端需求大,因此 JavaScript 异常繁荣。慢慢地,JavaScript 的性能越来越好,有更多人使用,进而写 JavaScript 的人想用 JavaScript 写更多的东西,一步步迈到了各个技术生态。
三足鼎立:Vue.js 、Angular.js 、React.js
2016 年,从繁杂的生态、无尽的争吵和选择当中, Web 开发中的 Vue.js 、Angular.js 、React.js 这三个框架初露端倪,各占据一片江山。所说的三足鼎立有一个前提,并不是它们在社区里有多么火或者人们都爱用,而是这些库是否被当下最新的应用直接用在自己的业务代码当中。
Angular.js 在 Google 已经被推了很多年,支持了 Google 本身及很多公司的大型业务代码。React.js 是 Facebook 支持的项目,它已经被用在很多线上的业务代码中,而且这些业务代码每天在承接着几亿的访问量。Vue.js 本身最开始是 Evan You 独立开发者开源的项目,之后 Alibaba、饿了么等公司都开始充分使用,现在阿里的 Weex 也借鉴了 Vue 的架构逻辑。
每个框架甚至都有了自己的技术生态
三个库三足鼎立的原因是它们本身都有一套自己的生态。例如 React.js ,最早底下的 Server Side APIs 、GraphQL 、Flux 层怎么样把静态数据状态管理系统管好,再到 React 层本身页面样式,再到 Virtual Dom 和 Native Code ,它的技术量不多,如果深入其中,学习周期也不长,但是它本身蔓延出了一条生态。如果有朝一日它把中间层做到足够好,上下层对接很多东西,React 会成为一个比较大的技术生态。
Why Vue.js
我们为什么选择 Vue.js,这是一个很幸运、很偶然的选择。掘金用 Vue.js 是在 0.12 版本,现在已经是 2.15 版本。当时选择最早版本的时候,掘金只有 4 个人。Vue.js 发展到现在,可以看到是一个增长非常疯狂的项目,从一开始的个人开源,到现在许多大公司使用,这和那些有大公司支持的开源库有了非常大的区别。到现在,Vue 在 NPM 上每月有超过 22 万次下载,这是很高的量。
为什么用 Vue.js ?
第一次使用 Vue.js 的时候,公司想做促销活动,写一个问答宣传页面,当时微信还没有禁止这样的传播,我做了一个“算算你值多少钱”的应用,当时脑子里有几个库。考虑到自己比较了解 Vue.js ,就试着用 Vue.js 来开发。后来发现从有这个想法到开发完只用了四个小时,包括 UI 层、页面层、微博分享、微信分享,开发小东西的速度超乎了想象,但那时候还没有准备拿它来写整个网站的业务逻辑。
Vue.js 到了 1.0 ,它是一个前端的 MVVM 的框架,看到一个网页端的界面,它出现这样的样式一定是因为它背后有数据。而 MVVM 框架最大的特点是样式随着数据变化而变化,数据和 UI 层的同步是框架本身自动完成的,这是 Vue.js 在当时帮我们解决的一个问题。Vue 到了 1.0 , MVVM 的框架适合掘金当时的业务开发需求。
图 2
发展到 2.0,很多人说 Vue.js 已经很火了,很多人真正愿意用它的原因是这张图(图 2 ),它是一个渐进式前端解决方案。分了五层很重的东西,不是打包型的,而是一个把它拆散了,每一层根据需求会加的东西。最早期人们用 Vue.js 的需求,这是一段前端的业务逻辑,希望用声明式语言 Declarative 把这段业务描述清楚,因此就可以用 Vue.js 最简单的业务逻辑、最简单的库把 Vue.js 这个库加进来,便可以完成前端业务里面的交互。从数据层到 UI 层的变化,特别简单的一个功能。但是前端应用更复杂一点,这个页面有很多组件,可以根据自己的需求去定义 Component ,可以用组建化的逻辑编写业务逻辑,这是第二层。但是发现这个东西很复杂,一个页面已经不能实现,要分好几个页面。可以用另外一个援引的库,就像 Routing 加进来,有了前端路由。
现在发展这个业务越来越复杂,因为这个业务正好代表了公司自己的发展,刚开始掘金只是单纯的 MVVM ,后来有了前端路由,再后来发现,这个页面已经复杂到类似于小应用,小应用一定会带来状态管理。在这个网站上,所有的应用都要同步当下登录的用户,这时必须需要状态管理,掘金便开始进行大规模状态管理。
前端已经复杂到需要完整的一套技术体或者自动化工具,来生产 Build 测试、发布等等,还要前端分包,这个页面是纯前端应用,不断地打开新的页面,其实它都是从后端再拿一个新的 js 出来,每一段页面都是自己的 js ,这样能提高性能,按需拿取页面的逻辑,这个时候分包就一定要用工业化的逻辑来实现。再往后走,可能会有一些测试、单元、代码的东西,它是一套整个的构建工具。
这就是一套流程,对于刚开始的开发者可能用特别简单的 Vue.js 代码写一个特别帅的主页,能动一动,弹一弹,后来可以根据自己的需求修改,页面可以更复杂,可以写成组件化的、写客户端路由等等。这一套渐进式的系统,使得几乎每一个业务在用 Vue.js 的时候都有一个对标点,一个网站的对标点可能是在客户端流这一层,可能一个网站的对标点是在扩展工具。因此,一个人基于自己要做的业务,可以按照不同的深度去使用,而且在不同的深度之下不会有性能或者学习路径陡峭的问题,这就是人们喜欢用 Vue.js 的真实原因。
Vue.js 原理
Vue.js 不支持 IE8 及其以下,它只支持 IE9 以上,因为 IE9 支持了 ES2015 。比如说 A 是一个 Object ,每次输出 A 到 B 的时候,一定会先调用一次 getter ,相当于获取了任何一个数据被改变的时候的那个事件,并且对于这个事件可以进行相关的处理。
图 3
这是一段业务(图 3 ),这个业务可能基于相关的 Object 的数据,因为有 setter 函数在这里控制,因此可以生成一个 watcher 。面对每一段业务代码,这个 watcher 都会关注所有相关的数据,以至于这些相关的数据发生任何的变动,它都会调动 setter 。setter 会告诉 watcher ,watcher 知道跟这段道路相关的数据发生变化了,发生变化之后就会去 Component Render Function,把新的数据的样式给前端样式,这样完成了从数据层变化,到告诉 watcher ,watcher 再告诉 Render Fenction,最后把前端 UI 变了这样的逻辑。它并没有用高级的数据结构或者高级的算法,它其实是用了 JavaScript 原生的一个属性。
选择 Vue.js 框架
选择一个前端框架一定有很重要的原因:
-
开发效率:Declarative Rendering ,前端开发写这个业务逻辑会非常漂亮;
-
代码维护:组件化 vue-loader ,可以在一个文件中关于某个组件或者某个页面写出逻辑层、样式层,可以写在一个组建中,这是一个比较好的解决方案。
-
速度性能:要能满足需求,Vue.js 是远快于 1.0 的。页面渲染的时候可能不在意性能,但是到页面复杂度的时候便会很在意性能,性能慢的时候会影响每一个页面跳转。
掘金 Vue.js 架构
每次做一个新的页面或者新的业务都会这样操作,后端要做自动渲染、自动更新,会有一套配置文件来配置前端进行分包和不停加载,不停地把前端的业务融合在一起。在每一个页面中最重要的一定是核心应用,在核心应用中每次首要考虑的是路由,对于整个产品或者小的功能点是否是有一些共用的状态。
定义好核心的应用清楚情况下,在页面里面找基础组件,并且把相关的基础组件比较复杂地组合成一个公用模块。基础组件在上层调用组件的时候,上层可以进行小的微调,但是这些组件的组合可能是有公用模块,模块的意思是在上层使用这个组件的时候,不可以再对这个组件进行任何的调整。再往下走是 Vuex ,也就是各个不同的分页,这个分页相关的业务逻辑,每次定义一个分页,要把前端路由定义好,并且把分页里面需要的状态拿好,把需要的组件和公用模块拉进来,这个页面的业务及直接单独写即可。
图 4
这是掘金一套前端的架构(图 4),但是前端架构相比于后端架构,往往简单很多。
纯前端应用的弊端及问题
兼容问题
这三个库( Vue.js , React.js , Angular.js: IE9+)都不支持 IE8 ,IE9 支持 80% 左右,偶然触及到一些 Vue.js 很底层很极端的性能时,IE9 会挂掉,除此之外基础性的还不错。但是给企业端或者后端特别复杂的页面,给工业用的 admin 页面可能用的还是 IE6、7、8 的浏览器,还不太能覆盖这部分的需求。
SEO
纯前端应用,如果看 Google 或者百度拉出来的数据,Google 做了一个后端的 cache ,跑了一个小的 Chrome 内核在后端,它能拉取完全的纯前端应用。而百度的机器一拉出来就是空的白页面,什么也没有,并不是百度的技术达不到。
第一,可能是百度面对大多数的技术网站生态还没有很多的纯前端应用。
第二,在后端小内核用纯前端应用去抓挺费性能的,觉得没有必要加这一层。但是面对中国的环境, Google 的流量不少,但是也有百度的流量,掘金要支持百度的 SEO ,但是还有其他的 SEO ,国内的 SEO 其实都不太支持,搜狗支持,其他都不太支持纯前端应用的抓取,对于内容型网站来讲可能是一个坑。
速度
初始的拉取速度,如果是网页的话,拉一个 HTML ,内容拿到了,开始往下看。掘金网站的真实情况,速度还好,该出来的东西一秒之内都能出来,但是第一次拉一个 HTML ,再拉一个 js ,再拉数据,再渲染页面,页面出来再拉分别的数据。其实这套流程中,在 HTML 拉出一批小的数据出来。如果很追求性能极致的人是不太能接受的,而且永远无法解决。因此,如果很在意初始页面第一次 loading 速度的人,可能这里会有问题。掘金现在已经有问题了,网站会在一个月内内容型页面会变成完全后端渲染。
URL <=> Content Cache
纯前端应用能够做到的极致是每一个资源都有一个 URL ,但是纯前端应用很大的一个问题是:并不是每一个资源都有固定的 URL ,大多数的页面都没有一个固定的 URL ,这样使得 cache 很难做。
每个页面都要定义分页的相关逻辑,大多数的开发者如果没有到达工业化或者产品没有到达一定的数据量级,写得很乱,并没有做到每一个页面斗殴自己的 URL 无,主流的 Cache URL 模式很难执行。但是当产品不断地优化,优化到一定的情景一定开始要提速的时候,纯前端应用就会遇到极大的问题。
Vue.js 2.0 后端渲染
A Simple Vue.js Program
Virtual DOM
经常听说 Virtual DOM 很厉害,其实 Virtual DOM 就是把 HTML 用 JavaScript 来表现,它不是任何特殊的技术,没有任何的功能点,可以用 HTML 来表达一段 DOM ,也可以拿 JavaScript 来表现一段 DOM 。最大的不同点在于,多了一层把 JavaScript 定义的 Virtual DOM 渲染成真实 DOM 的这套业务逻辑。比如,这是一个 Virtual DOM ,先把这个 Object 里面再加一个 ul ,可以用 Virtual DOM 来实现,为什么说它的性能好呢?因为在浏览器环境中,HTML 或者 DOM 的直接运算非常慢,但是 JavaScript 运算很快。
Render Function
图 5
有了 Virtual DOM 这一层用 JavaScript 代表 DOM 之后,用 Render Function 把 DOM 再刷出去即可。因此,Render Function 也是 2.0 实现的,1.0 只能定义页面和逻辑,它来帮你做一切,而 2.0 之后可以用 Render Function ,这是一段把 Virtual DOM 变成 DOM 的逻辑(图 5 )。
最大的价值在于,因为有 Render Function ,把 JavaScript 变成真实 DOM 这个函数,同样把后端能理解的 Object 在后端提前用 Render Function 输出 HTML ,这样后端就已经把它输出来了,直接 Drive 给前端,这个页面就已经有了。也可以把一个 JavaScript 表达的 DOM 输出成真实的 HTML 给前端,后端渲染就完成了。
Stream
只要在 Vue 业务包在一个 function call 中并接上 Window contex,服务器 renderer 拿到相关业务 js 文件吐出内容。Vue.js 2.0 支持 Stream 后但流式数据,在 HTML 完整生成之前的向前端吐数据。
后端渲染 Nuxt.js 的开发实践
Vue.js 最基础的后端渲染,如果对于这样一个业务,每个公司都要根据自己的业务代码做一套后端渲染的逻辑,这不太可能。对于通用解决方案,一定是有更好的库,感谢有人造轮子。刚开始做后端渲染的时候是没有轮子的,掘金后端渲染都是自己写的,现在如果有轮子会好些。
开源支持
Vue 的生态繁荣,很大一部分来源于整个生态周边环境的支持,比如脚手架、组件化、路由、状态管理、 Ajax 、前端开发工具、前端组件库、后端渲染。在 Vue 的前端方案上,中国已经比国外强,开发质量很高。后端渲染,迟早会有一个很牛的库出来帮大家,很可惜之前没有,但是最后有了,叫做 Nuxt.js 。
Nuxt.js 是一个类似于 Next.js(React)的开源后端渲染库,它支持的并不是后端渲染这一层的业务,它做了一套通解,想要用 Vue 的业务去开发,但同时支持 code-splitting 、generation 等不同的配置文件,它都会有一套不错的解决方案生成。但是大家都是后端的高手,最终可能不愿意用别人的解决方案。但是像比较偏前端的人来讲,它的基础解决方案已经解决很大问题了。
Nuxt.js 文件结构
它里面有几个基础的文件定义,其中最重要的是 nuxt.config.js 。把分包打包的逻辑封装到底层,这是现在最大的问题,因为有功能在这一层会做测试、静态的传输和存储,这也是为什么掘金不能直接去用 Nuxt 完成后端渲染,还是要自己写。最重要的是 Asssets 基础业务代码和第三方代码的存储文件,即 Vue 里面不同页面的这套逻辑。把一个页面放在 pages 里面之后,就不用专门定义,它会自动绑定好。
Nuxt.config.js
head 定义的是后端渲染这套业务的时候,在网页端的 head 里面放哪些基础数据,比如 meta 等数据,以及 link 里面有哪些静态文件需要特别注意的,如何援引于其他资源,比如 css 里面掘金是从 assets 里面拿出来的,它的分页之间的切换,纯前端应用不需要看到页面里面有一个 loading 的感觉,它解决切换时候的动效,把它封装得很漂亮。
pages
对于 Vue 来讲,把它的 template 侧写在一个 export 的文件里面,layout 、transition 和 scrollToTop 是纯前端应用都会遇到的问题,这套页面用的是哪个 layout 展示?在页面切换之间是否要有动画效果?以及在纯前端应用中每次页面之间切换是否要滚到最上面?因为它是一个单纯的页面,如果不设置滚到最上面,会发现跳到另外一个页面还是在中间的位置,但是在浏览器来看其实是在一个网页里面,没有跳到新的网页,它把通用的需求封装得很漂亮。validate 是解检测 url 的,middleware 是一些其他的功能,可以再加进去。这里面最好的事情是 head ,在纯前端应用中会有不同的页面,在每个页面中 title 一定会变,单独页面里面移动端的展示模式和特殊的配置文件等等,这一套东西以前都得单独来写,每一个页面都得单独解决,而现在通解来实现了,而且通解没有做得太深,有时候开源库定义得太死,可活动性太差,但是它定义好的东西都是所有人需要的。
Async Data
拉数据,从远端拉数据,再渲染页面。
Vuex/Fetch
Fetch 和 data 几乎是一样,唯一的不同在于 data 这个函数是页面渲染出来的,拉数据的时候在渲染页面的更多样式。打开一个页面,Fetch 要先把页面拉回来,这个页面才会跳转。为什么要 Fetch ?因为对于后端渲染来讲,一定是在后端渲染,一定是先把数据拉回来,才能把页面生成,才能投给前端。所以,Fetch 函数是用后端渲染很重要的一个环节。
Vuex/nuxtServerlnit
Vuex 就是一个状态管理器,也就是一个前端应用所有的数据都需要的地方。而这里需要什么呢?所有的后端页面也需要用户认证,并且把用户数据给前端,但是对于纯后端应用生成页面稍微有点难,但是在 Vuex 里面定义好所有页面都需要公用这块逻辑,并且用 nuxtServerInit 提前在后端也把这个需求、这个解取好,用这一套完整定义可以使得前端、后端再输出页面,不管是前端输出的还是后端渲染好的,都可以同步获得这个数据,并且完成这部分业务。它解决了非常大的业务逻辑,如果让自己写,代码量少说也得四五百行左右,它解决得非常好,掘金把源码拿出来看明白,把这段源码应用到产品里。
总结
前端框架虽好,但是还是需要后端渲染。Vue.js 后端渲染技术层已算成熟。Nuxt.js 等库优化了后端渲染的实现效率。交互型产品适合前端应用,内容型产品适合后端应用。
历史 ECUG 精粹系列:
谢孟军:The State of Go
七牛云大数据平台建设实践
Go 在游戏行业中的工程实践
后端渲染实践——看掘金社区是如何实践的