【大前端之前后分离01】JS前端渲染VS服务器端渲染
2015-04-29 06:06
393 查看
前言
之前看了一篇文章:@Charlie.Zheng Web系统开发构架再思考-前后端的完全分离,文中论述了为何要前后分离,站在前端的角度来看,是很有必要的;但是如何说服团队使用前端渲染方案却是一个现实问题,因为如果我是一个服务器端,我便会觉得不是很有必要,为什么要前后分离,前后分离后遗留了什么问题,如何解决,都得说清楚,这样才能说服团队使用前端渲染的方案,而最近我刚好遇到了框架选型的抉择。来到新公司开始新项目了,需要做前端框架选型,因为之前内部同事采用的fis框架,而这边又是使用的php,这次也就直接采用fis基于php的解决方案:
http://oak.baidu.com/fis-plus
说句实话,fis这套框架做的不错,但是如果使用php方案的话,我就需要蛋疼的在其中写smarty模板,然后完全按照规范走,虽然fis规范比较合理,也可以接受,但是稍微深入解后发现fis基于php的方案可以概括为(我们的框架用成这样,不特指fis):
服务器端渲染html全部图给浏览器,再加载前端js处理逻辑
显然,这个不是我要的,梦想中的工作方式是做到静态html化,静态html装载js,使用json进行业务数据通信,这就是一些朋友所谓的前端渲染了
JS渲染的鄙利
前端渲染会带来很多好处:① 完全释放前端,运行不需要服务器;
② 服务器端只提供接口数据服务,业务逻辑全部在前端,前后分离;
③ 一些地方性能有所提升,比如服务器不需要解析index.html,直接返回即可;
④ ......
事实上以上的说法和优势皆没有十足的说服力,根据上述因素,我们知道了为什么我们要采用js+json的方案,但这不代表应该采用。
比如很多朋友认为前后分离可以让前端代码更加清晰,这一说法我就十分不认同,如果前端代码功力不够,绝对可以写成天书,分离是必要条件,却不是分离后前端就一定清晰,否则也不会有那么多人呼吁模块化、组件化;而且服务器端完全可以质疑这样做的种种问题,比如:
① 前端模板解析对手机端的负担,对手机电池产生更快的消耗;
② 前端渲染页面内容不能被爬虫识别,SEO等于没有了;
③ 前端渲染现阶段没有完善的ABTesting方案;
④ 不能保证一个URL每次展示的内容一致,比如js分页导致路由不一致;
⑤ ......
以上的问题,一些是难点,一些是痛点,选取前端渲染方案至少得有SEO解决方案,不然一切都是空谈
所以有如此多的问题,前端凭什么说服团队使用前端渲染的方案,难道仅仅是我们爽了,我们觉得这样好就可以了吗?
况且现状是团队中服务器端的同事资深的多,前端话语权不够,这个时候需要用数据说话,但未做调研也拿不出数据,没有数据你凭什么说服领导采用前端渲染方案?
为什么要采用前端渲染
最近两年我却找到了可以说服自己采用前端渲染的原因:① 体验更好
② Hybrid内嵌只能用静态文件
事实上我们不能用数据说明webapp(前端渲染)的体验就一定比服务器端渲染好,所以Hybrid内嵌就变成了主要的因素,现有的Hybrid有两种方案:
① webview直连线上站点,响应速度慢,没有升级负担,离线应用不易;
② 将静态html+js+css打包进native中,直接走file模式访问,交互走json,非常简单就可以实现离线应用(某些页面的离线应用)
现在一个产品一般三套应用:PC、H5站点、APP,PC站点早就形成,H5站点一般与APP同步开发,Hybrid中的逻辑与H5的逻辑大同小异,所以
H5站点与Hybrid中的静态文件使用一套代码,这个是使用前端渲染的主要原因,意思是H5程序结束,APP就完成80%了。
因为服务器端渲染需要使用动态语言,而webview只能解析html等静态文件,所以使用前端渲染就变成了必须,而这一套说辞基本可以说服多数人,自少我是信了。
拦路虎-SEO
上面说了很多前端渲染的问题,什么手机性能、手机耗电、ABTesting都不是痛点,唯一难受的是H5站点的SEO,以原来公司酒店订单来说,有20%以上的流量来源于H5站点,浏览器是一个流量的重要来源,SEO不可丢弃。所以前端渲染必须有解决SEO的方案,并且方法不能太烂,否则框架出来了也没人愿意用,好在这次做的项目不是webapp,SEO方案相对要简单一点,移动端展示的信息少SEO不会太难,这个进一步降低了我们的实现难度,经过几轮摸索,我这两天想了一个简单的方案,正在验证可行性。
JS渲染应该如何做
前端渲染应该如何做?阿里的大神们事实上一直也在思考方案,并且似乎已经有成功的产出:前后端分离的思考与实践(二)可惜,读过文章后,依旧没有获得对自己有用的信息,并且对应的代码也看不到,自己之前的方案:探讨webapp的SEO难题(上),连自己都觉得非常戳而没有继续。
编译的过程
而最近在公司内部使用fis时候,一段代码引起了我的兴趣:{%block name="body"%} {%widget name="webapp:widget/index/route/route.tpl"%} {%widget name="webapp:widget/index/searchCity/searchCity.tpl"%} {%widget name="webapp:widget/index/selectDate/selectDate.tpl"%} {%/block%}
这段代码基于smarty模板,运行会经过一次release过程,将真正的route模板字符串与服务器data形成最终的html,这段代码引起了我的思考,却说不出来什么问题。
我偶然又看到了之前的react解决方案,似乎也有一个编译的过程:
React.render( // 这是什么不是字符串,不是数字,又不是变量的参数……WTF <h1>Hello, world!</h1>, document.getElementById('example') ); //JSX编译转换为javascript==> React.render( React.DOM.h1(null, 'Hello, world!'), document.getElementyById('example') );
所以,在程序真实运行前有一个编译的过程,一个是编译才能运行,一个是运行时候需要编译,于是我在想前端渲染可以这样做吗?
页面渲染的条件
比较简单的情况下,对于前端来说,页面html的组成需要数据与模板,而服务器也仅仅需要数据与模板,所以简单来说:html = data + template
前后端的模板有所不同的是:
前端模板也许不能被服务器解析,如果模板中存在js函数,服务器模板将无法执行
但是经过我们之前的研究,.net可以运行一个V8的环境帮助解析模板,java等也有相关的类库,所以此问题不予关注,第二个问题是:
前端数据为异步加载,服务器端为同步加载,但是:
简单情况下,服务器端与前端数据请求需要的仅仅是URL与参数
于是,一个方案似乎变的可能。
前端渲染方案
入口页
将如我们的index.html是这样的:debug端:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="./libs/zepto.js"></script> <script type="text/javascript" src="./libs/underscore.js"></script> <script type="text/javascript" src="./libs/require.js"></script> </head> <body> <%widget({ name: 'type', model: 'type', controller: 'type' }); %> </body> </html>
其中name对应的为模板文件,而model对应的是数据请求所需文件,controller对应控制器,我们这里使用grunt形成两套前端代码,分别对应服务器端前端:
注意:这里服务器实现暂时使用nodeJS,该方案设想是可以根据grunt打包支持.net/java/php等语言,但是楼主服务器战五渣,所以你懂的
服务器端:
<!DOCTYPE html> <html> <head> <title>测试</title> <script type="text/javascript" src="./libs/zepto.js"></script> <script type="text/javascript" src="./libs/underscore.js"></script> <script type="text/javascript" src="./libs/require.js"></script> </head> <body> <%-widget({ name: 'type', model: 'type', controller: 'type' }); %> </body> </html>
前端:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="./libs/zepto.js"></script> <script type="text/javascript" src="./libs/underscore.js"></script> <script type="text/javascript" src="./libs/require.js"></script> <script type="text/javascript"> require.config({ "paths": { "text": "./libs/require.text" } }); var render = function (template, model, controller, wrapperId) { require([template, model, controller], function (template, model, controller) { //调用model,生成json数据 model.execute(function (data) { data = JSON.parse(data); if (data.errorno != 0) return; //根据模板和data生成静态html,并形成dom结构准备插入 var html = $(_.template(template)(data)); var wrapper = $('#' + wrapperId); //将dom结构插入,并且将多余的包裹标志层删除 html.insertBefore(wrapper); wrapper.remove(); //执行控制器 controller.init(); }); }); }; </script> </head> <body> <div id="type_widget_wrapper"> <script type="text/javascript"> render('text!./template/type.html', './model/type', './controller/type', 'type_widget_wrapper'); </script> </div> </body> </html>
虽然,我这里grunt的程序尚未实现,但是根据之前的经验,这是一定能实现的。
model的设计
默认入口端model为一个json对象debug端&服务器端:
{ "url": "http://runjs.cn/uploads/rs/279/2h5lvbt5/data.json", "param": {} }
因为服务器端仅仅需要一个url一个param,所以服务器端与debug端保持一致,而前端被grunt加工为:
define(function () { return{ url: './data/data.json', param: {}, execute: function (success) { $.get(this.url, this.param, function (data) { success(data); }) } }; })
显然,此数据源文件比较简单,真实情况不可能如此,我们这里也仅仅做demo说明,后续逐步加强。
服务器端运行流程
服务器端由于是基于node的,首先需要配置app,这里将所有路由全部放到index.js中:<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="./libs/zepto.js"></script> <script type="text/javascript" src="./libs/underscore.js"></script> <script type="text/javascript" src="./libs/require.js"></script> <script type="text/javascript"> require.config({ "paths": { "text": "./libs/require.text" } }); var render = function (template, model, controller, wrapperId) { require([template, model, controller], function (template, model, controller) { //调用model,生成json数据 model.execute(function (data) { data = JSON.parse(data); if (data.errorno != 0) return; //根据模板和data生成静态html,并形成dom结构准备插入 var html = $(_.template(template)(data)); var wrapper = $('#' + wrapperId); //将dom结构插入,并且将多余的包裹标志层删除 html.insertBefore(wrapper); wrapper.remove(); //执行控制器 controller.init(); }); }); }; </script> </head> <body> <div id="type_widget_wrapper"> <script type="text/javascript"> render('text!./template/type.html', './model/type', './controller/type', 'type_widget_wrapper'); </script> </div> </body> </html>
View Code
整体目录
PS:目录有一定缺少,因为程序尚未完全完成,而最近工作忙起来了......问题&后续
因为这个方案是自己想的,肯定认为是有一定可行性的,但是有几个问题必须得解决。debug烦
如所示,开始阶段我们一般都只开发debug层,但是要调试却每次需要grunt工具release一下才能运行client中的程序,显然不好,需要解决。模板嵌套
模板嵌套问题事实上是最难的,想象一下,我们在一个模板中又有一个widget,在子模板中又有一个widget,这个就变成了一个噩梦,这里的嵌套最怕的是,父模块与子模块中有数据依赖,或者子模块为一个循环,循环却依赖父模块单个值,这个非常难解决。后续
这个想法最近才出现,刚刚实现必定会有这样那样的问题,而且自己的知识体系也达不到架构水平,如果您发现文中任何问题,或者有更好的方案,请您留言,后续这块的研究暂时规划为:① 完善grunt程序,形成.net方案
② 解决debug时候需要编译问题
③ 解决模板嵌套、模块数据依赖问题
④ ......
github
https://github.com/yexiaochai/sword微博求粉
相关文章推荐
- stenciljs 学习十 服务器端渲染
- reactjs服务器端渲染——node搭建简易服务器
- 前端动画渲染引擎pixi.js系列(9)如何生成缩略图
- JS前端三维地球渲染——中国各城市航空路线展示
- JS前端面试题-总结01
- [转]谈谈前端渲染 VS 后端渲染
- 前端浏览器辅助知识总结--渲染机制、JS运行机制、页面性能、错误监控
- 前端动画渲染引擎pixi.js系列(2)container和Sprite的概念和继承关系
- 前端动画渲染引擎pixi.js系列(4)如何实现鼠标交互事件
- 前端动画渲染引擎pixi.js系列(5)Text文本和Graphics画笔工具
- 前后端分离:前端人员做页面与渲染,后端做接口
- [笔记]前端html设计与js代码分离
- WEB前端性能优化:HTML,CSS,JS和服务器端优化
- [ 前端 ] xheditor!渲染 JS 动态生成的textarea!
- Vue.js 2.0源码解析之前端渲染篇
- 前端动画渲染引擎pixi.js系列(8)动画精灵AnimatedSprite实现逐帧动画效果
- 【JavaScript】前端开发框架三剑客—AngularJS VS. Backone.js VS.Ember.js
- 前端工程化之Nodejs+Angular+webStorm+VSCode安装与配置
- angularjs(二):前端开发框架三剑客—AngularJS VS. Backone.js VS.Ember.js
- 前端动画渲染引擎pixi.js系列(1)简单介绍及应用