(认真把这篇文章看完,保证你会学到不少,若是没学到请联系做者或直接报警)javascript
随着前端技术的不断变革,前端从后台吐页面 -> 前端MVC -> mvvm、react -> node直出 ->同构跨端的大体发展方向(后面我也讲下前端的跨终端实现),前端正在向着模块化、高效性、跨端性扩展。过渡完mvvm、react和node直出的阶段,前端工程师们又开始在通日后台同构的道路上探索。并且也出现了一些列可能的方案,例如使用mvc、mvvm或react在服务端作实现,不管如何。可是咱们不得面临一系列问题。同构的目的?同构的核心?同构的代价?同构的优点?css
咱们先来看下这三个问题。html
首先说下目前比较主流的web先后端分离方案:这种方案将web站点分为前端和后台,前端经过拉取后台数据的到页面再经过js模板渲染到页面上。几个主要问题,一是页面显示须要等后台请求(cgi等)返回来才能渲染,二是seo怎么办,同时也承受开发联调效率的考研。前端
因此node直出的方案在阿里、腾讯等大的前端团队里陆续被使用起来,思路是经过node直出首屏内容和关键性seo信息解决了上述问题。vue
可是问题是咱们没法所有直出页面全部的内容,一般由于太大,因此前端仍然须要维护原有的前端代码。更多状况下,同一个站点中,咱们更但愿的是在某些场景下使用前端渲染,另外一些状况下使用后端直出(例如,但愿hybridapp在没有离线包的第一次直出,后面不须要下载静态文件时使用前端拉取渲染的方式,或者在高级的浏览器下使用http2前端渲染,低端浏览器上则使用直出),结果咱们不得不维护两套不一样的先后台代码,尽管可能都是用js写的。因此同构但愿解决的是维护先后端维护两套代码的问题。java
同构但愿作的事情是只开发一套项目代码,既能够实现前端的渲染也能够作后台的直出。为何能够这样作呢?由于后台直出页面在后端生成,实现的方式也是经过数据加上模板编译的方式生成,前端渲染和后台直出的模式生成dom的区别只在于数据和模板的渲染发生在何时。node
同构的目的是为了统一先后台的方案,天然也会牵扯到先后端的适应性修改。例如前端的数据渲染如何与后台直出保持一致、后台如何处理异步的问题、在原有业务上的实现代价、和原有前端框架的的冲突性等等。若是考虑到这些问题,同构改造实现的代价就会很大,毕竟它是综合了两个开发人员的工做量。mysql
同构的网站应该具备一些优点:一、能够根据用户须要方便的选择前端渲染仍是后台支持;二、开发者真的只须要维护一套代码,固然这个是不严谨的,后台多出的工做是配置路由和数据接口编写,但和前端dom相关的指维护一套;三、前端的模块化开发和后台全部模块是共享的;四、能够避免先后端工程师的联调沟通成本,但总体成本会比单我的开发大;四、开发构建调试系统完善。react
这个被讨论的比较火。但事实上,目前直接使用react都比较困难,而react的应用如今就少,并且也没见过大型应用使用,因此同构的价值不大,其实对于此实现方案在开发时的配置依然比较多。不过技术上react同构是一个可行度很高的方案。jquery
想多了解的话能够看下《isomorphic-reactjs》,其核心思路是使用renderToString将virtual dom直接转化成为html,由于virtual dom在先后端均可用。这样就实现了直出的转换。然而我想说的是,reactjs项目里面html和js混淆,模板语言生硬,渲染和事件绑定在一块儿,行为和结构层不分离只使用js来管理,原本对通常人来讲技术学习成本高,项目大了很难管理,有可能带给咱们至少两倍以上的成本。
相比来讲这个可行性稍大些。例如你只须要在服务器端实现一个mvvm的核心,经过本身实现dom分析器其来解析后端模板中的directives、filter、和事件bind就能够了,可是你要去作这个mvvm核心,并且若是你抛开主流的mvvm框架去作,并且要和前端使用的mvvm框架解析同样,除非若是保证你写的框架足够优秀能,或者被别人接受。固然一个可行的方案是彻底根据现有某个主流mvvm框架(例如vuejs、avalon等)的语法来在后端实现一个功能相同的解析插件,由于现有主流的mvvm是不能在node上解析模板的。
这样的话开发的代码就必须是使用mvvm的模板来写了。固然根据mvc框架来实现的原理相似,不过能够灵活些。以前咱们作过相似的事情,实现了vuejs的一些基础的directive和filter解析,可是咱们业务前端都没有不少人使用vuejs,作同构很像后端上的一厢情愿。最后放弃了。
不管是react仍是mvvm来实现,其实咱们要弄清楚同构须要作的最核心的一件事情是保证一个数据渲染机制(react是virtual to html、mvvm是view模板)在先后端都能正确解析。因此保证这件事情实现了问题是否是就简单了。使用react或mvvm只是说咱们能够更好的作前端模块化管理。
大多状况下,咱们更多推荐使用mvvm管理vm模板,由于这样就能够模板重用了,可是咱们也不得不考虑实现后端mvvm解析模板的代价。但其实在tpl模板层面只是须要一个通用模板,一个能同时支持后台和前端模块化开发的模板。mustache、handlebar、jade、ejs、artTemplate、kissy?彷佛有不少选择,可是他们要么都只能在一端 工做,要么功能较简单,都不能直接解决问题。可是这个问题必须获得解决。
经过分析,因此整理下咱们须要解决的几个问题:
基于如今的的node端发展趋势,koa相比经典express(其它的一些不主流框架这里就不比较了,你们也能够去了解)有了不少优点,koa自实现的generator特性能用来写没有callback的异步处理,而使用express须要配合bluebrid使用promise特性;koa 不在内核方法中绑定任何中间件,它除了提供了一个轻量优雅的函数库,还包含错误处理机制,并相对于express某些功能的语法糖使代码更简洁,另外generators 实现了的级联中间件,控制权在中间件之间传递很清晰,然后面这些express都不能直接作到。
固然后端除了这些,还须要关注数据库层的问题,通常使用mongodb、mysql或redis都OK,具体根据业务场景决定,并且同时也能够搭配pm2来进行进程管理。
为了兼容旧的框架,咱们仍是须要兼容到jquery/zepto,固然也但愿可以兼容到mvvm的框架。这方面处理的方案能够比较灵活,而是用后端mvvm直出的方案可能就兼容不了jquery的程序了。因此使用通用模板后你要用react都不会有任何问题。
swigjs是node端的一个优秀简洁的模板引擎,可以根据路径渲染页面、支持面向对象的模板继承、页面复用、支持动态页面、而且使用简洁能快速上手,目前不只在node端较为通用,相对于jade、ejs优秀,并且在浏览器端也能够很好的运行。但问题是,即便直出后的状况下浏览器上仍然有异步渲染的状况,是否是直出后也须要引入这个swigjs模板库呢?何况就算是浏览器渲染的方式,须要加载这个模板库,并且模板是动态编译的,必定会慢。 对于这些问题,咱们但愿是前端渲染状况下在构建阶段就可以完成模板编译,固然swig在后端的解决方案是没有任何问题的。
为此咱们仍是必须一个和swig相似的前端静态编译插件。幸运的是做者已经为你作了这件事情:
npm install fis3-parser-swig
,使用时配置以下(暂不支持浏览器端模板继承):
.match(/.+\/(.+)\/.+\.tpl$/, { // js 模版一概用 .tpl,可使用[模块名.tpl]做为模板 isMod: true, rExt: 'js', id: '$1.tpl', moduleId: '$1.tpl', release: '$1.tpl', // 发布的后的文件名,避免和同目录下的 js 冲突 parser: fis.plugin('swig') })
它的优点是配置后能够在构建阶段就将前端的模板文件编译成js文件,就不用在页面打开是加载swig模板引擎作渲染了。这样具体的项目代码请参见最后面的github地址。
构建上仍是基于fis3上开发,固然也能够去自由选择gulp或其它的,我的习惯fis3的一些优点,这里就不展开讨论了,若是想使用es6也可使用下做者以前作的插件fis3-parser-babel
,具体这里就不展开介绍了。这里先看下整个项目开发目录的设计:
|--asyncWigdet #前端异步模块的存放目录,这里渲染和直出加载的是相同的 |--testMod #前端异步加载的模块 |--mock |--indexPage.json #mock的开发调试数据目录 |--modules #前端扩展库,例如angular或jquery的插件等 |--libs #前端基础库,例如angular、jquery等 |--pages #页面入口模块 |--index |--index.html #模块html |--index.tpl #模块模板 |--main.js #模块对应的js |--index.scss #模块对应的css |--server #服务器端内容 |--lib #服务器端基础库,例如db链接服务基础模块等 |--mock #mock的开发调试数据目录 |--models #数据库model文件 |--pages #前端编译后的view模板 |--routes #koa route |--views #后台若不使用模板,也能够直接使用这里无编译的view |--index.js #koa入口任务脚本 |--pm2.json #pm2管理配置 |--widget #页面内部业务模块 |--search-bar |--index.html #模块html |--index.tpl #模块模板 |--main.js #模块对应的js |--index.scss #模块对应的css |--- ... |--fis-config.js #fis打包配置文件 |--dev #前端渲染页面输出目录,固然这里和能够和编译出的node端模板使用同一个目录 |--index.html #入口页面
目录具体能够参考github地址:koa-fis3-isomorphic
根据这个目录总结一下:
这里编译出前端页面和后端的pages模板(和views相同功能,但避免和原生views命名冲突)是经过fis3配置的不一样任务来实现的,例如调试时可使用fis3 release dev
和fis3 release server
来完成不一样构建,暂时使用的一个目录
这里只须要维护一套代码目录规范,先后端打包进行两次(固然你能够合并下两个命令变为一条)
前端自动打包都是fis3完成的,这里咱们不须要去关心
前端的基础框架能够任意选择,这里只是用zepto作范例
受平时项目经验的启发,在koa + swig + fis3 + fis3-parser-swig的条件下,咱们配置url地址中的一个特定r参数来判断使用前端渲染仍是后台支持,例如:http://localhost:3000/index.html?r=1
使用前端渲染,不带r=1
则使用后台支持。服务端判断带有r=1
则转到前端的html服务器上,前端判断r=1
则调用数据render方法;不然后台直接渲染模板,前端不作数据render,只作事件绑定。这样简单可靠的解决了这个问题。
var getUrlParam = function(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象 var r = window.location.search.substr(1).match(reg); //匹配目标参数 if (r != null) { return unescape(r[2]); } return null; //返回参数值 }; // ... //前端摸个模块的处理 init: function(data) { // url中带有r,才作前端模板渲染 if (window.r) { this._renderData(data); } this._bindEvent(); }, _renderData: function(data) { this.$el.html(panelTpl({panel3: data})) },
总结一下完整的方案和思路:
在fis3构建和koa后端框架的前提下,使用swig来实现后端模板渲染,使用fis3-parser-swig来作前端页面的模板编译,最终可以在先后端支持统一个模板的解析。
同构的优点前面讲到了,这里说下几个须要注意的问题
这里的实现只表明我的思路,实现的具体组合方案有不少种,可是思想是一致的,例如使用gulp,或后台解析mvvm模板或mvc模板等。
模板fis3-parser-swig
不支持继承,由于是前端组件化环境编译的,不须要使用模板继承;node端能够任意使用
开发调试时文件watcch变化不生效问题。若是开发时fis3 watch文件变化和nodemon wactch文件变化冲突,可能致使前端代码不能自动生效。缘由是fis3要进行的文件改变的目录被nodemon进程占用,这是适当重启下server就行了,这里两个watch目的也是为了提高调试的效率。
使用同一套构建也是能够的。 不过须要注意的问题,静态编译后前端的模板在数据没返回是会显示模板的语法乱码(这个你们都不陌生了),一般解决思路是先让模板隐藏,数据渲染完后显示。这里建议是两次打包不一样的配置生成不一样的html:前端渲染方式静态编译时,模块主容器的内容就不要用模板了,动态引入的tpl模板会被编译成js去填充渲染模块的主容器。
若是使用后台直出后,须要加载的前端js仍然是和前端渲染时同样的,这里能够去作分离减少直出后加载的js文件大小。如今为了便于处理放在一块儿
原文: https://ouvens.github.io/frontend-build/2016/04/21/koa-fis3-swig-nodejs-isomorphic.html
有不正确的地方请大神指教~~