为什么你应该抛弃Express的视图渲染引擎
Nodejs Express框架的一个被人们广为使用的特性是它的渲染引擎。Express视图渲染引擎允许Controller提供一个视图名称和视图模型对象给Express,然后返回由HTTP响应流输出的一些字节。基于为eBay的Nodejs技术栈提供支持所获得的经验,我们发现了这个方法的缺点并决定彻底的弃用它。我们这么做了之后,能明显看到页面加载速度的提升、更好的模块性以及开发者生产力的提高。本文将解释为什么你不应该使用Express视图渲染引擎,并提供一个推荐的替代方案。
Express视图渲染引擎
在解释Express视图渲染引擎的缺点之前,让我们先来快速的过一遍它的用法。首先你必须使用类似下面的代码来配置你的Express app:
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
第一行代码告诉Express在views目录下搜索所有模板,第二行则在这些模板上应用jade模板引擎。
配置好之后,你能在Controller中使用res.render(viewName, viewModel)
方法来渲染视图,代码如下:
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
})
这里面的机制是,Express将视图名称对应到模板文件的路径,使用关联的模板引擎来渲染模板,将结果输出到response,最后结束响应。
不好的地方
这个方法看起来如此简单,你可能从来没想过它会出错。但实际上它还是有不少问题,当出问题时会影响web程序性能和可维护性,下面我们讲到这些。
破坏模块性
Express强制性的要求设置一个views目录,所有模板都堆放在这里。本来你可以将模板放到Controller或使用这些模板的UI组件附近,现在你不得不将它们放到一个单独的顶级目录里。一个典型的基于Express的项目,它的目录结构将会与下面类似:
routes/
home.js
login.js
…
views/
home.jade
login.jade
…
使用Express推荐的目录结构,你不得不将源码按照类型而非特性来分来。作为结果,紧密相关的文件将被分离到截然不同的目录树里面,这意味着如果你需要修改一个功能,你可能需要遍历整个项目的目录以修改不同的文件。在后面的替代方案里我会介绍一个更健康的、模块化的目录结构。
与Express高度耦合
当使用Express视图渲染引擎,意味着你在使用一个与Express高度耦合的特性。如果你想在客户端渲染一个模板呢?如何解析局部模板?如果你想切换到另一个web框架或者不使用框架呢?事实是,Express引入的这个解析和渲染模板的新方法,只能在服务端并且只能在Express中使用。当构建同构(isomorphic)web app时,我们希望在服务端和客户端使用更一致的渲染模板的方法。
没有Streaming
Express视图渲染引擎不支持streaming(流式传输),这对于客户端和服务端两方的性能都有负面影响。在服务端,当渲染一个HTML页面模板时,整个HTML输出都被当做一个超长的字符串存储在内存里,当整个HTML输出都构造完成之后,才开始向HTML响应里输出第一个字节。这是因为所有的视图引擎都必须使用回调来实现。而只有当整个HTML都被渲染完毕,回调才会被调用。不使用streaming来输出HTML,我们将浪费服务端的内存,以及增加客户端等待响应的时间。
使用streaming来传输响应的好处包括,更早的发送页面的<head>
部分,因此客户端能够更快的下载页面的CSS文件。为了感受streaming带来的好处,你需要使用支持streaming的模板引擎。此外,支持异步渲染的模板引擎(如Marko、Dust和Nunjucks)甚至能够在视图模型构造完成之前就输出响应,因此能带来更多的性能提升。
集中式的配置
并不是所有的配置都不好,但不必要的配置肯定是不好的。如果应用不同的地方需要不用的配置,集中式的配置将引起冲突。比如,如果需要多个view目录?Express的sub-apps能在一定程度上解决这个问题,但最好的办法的还是避免额外的配置。
解决办法
Express将它自己描述为一个框架,那么绕过Express视图渲染引擎实际上还是很容易的。这将让开发者创造更灵活的、易理解的和更高性能的应用。首先你必须理解一个重要的概念,res
对象实际上是一个可写的HTTP响应流(尽管它已经被Express重度修改过),因此你能够直接向响应里输出:
app.get('/', function (req, res) {
res.write('Hello Frank');
res.end();
});
如果你想渲染一个jade模板到HTTP响应,你可以照下面做:
var templatePath = require.resolve('./template.jade');
var templateFn = require('jade').compileFile(templatePath);
app.get('/', function (req, res) {
res.write(templateFn({name: 'Frank'});
res.end();
});
这个方法比起res.render()
有些啰嗦,但它很直观也更灵活。
注意:你可能注意到我们使用了require.resolve来获得模板的绝对路径,这个模板和我们的Controller模块放在一起。
如果你的模板引擎支持输出到一个流,代码将会更简单。比如Marko模板引擎的代码如下:
var templatePath = require.resolve('./template.marko');
var template = require('marko').load(templatePath);
app.get('/', function (req, res) {
template.render({name: 'Frank'}, res);
});
视图解析程序
如果你觉得你的app需要使用视图解析程序(比如你需要为A/B测试使用不同的模板,或者根据用户的地区来确定模板),也有一个非常干净的解决办法。下面的代码假想了一个独立于Express的视图解析程序,它是如何根据名称和一些上下文(本例中是请求和当前目录)来解析视图模板的。
var myViewResolver = require('my-view-resolver');
app.get('/', function (req, res) {
var template = myViewResolver.resolve('hello', req, __dirname);
template.render({name: 'Frank'}, res);
});
使用一个明确的视图解析程序能让代码更容易理解,并提供更高的灵活度。
项目结构
现在你可以不受只能有一个views目录的限制了。让我们来看看新的项目结构:
pages/
home/
index.js
style.css
template.marko
login/
index.js
style.css
template.marko
新的项目结构让关联的文件放在相同的目录,从此之后可以模块化的开发和维护项目了。
总结
尽管Express是一个极简主义的框架,但也没有必要一定要使用它所有的特性。有些时候独立的模块能比它做得更好。Nodejs其中的一条指导原则就是“模块应该只做一件事并把它做好”,并且我认为Express将诸如视图渲染和路由这些特性分离开来会更好。我们已经看到Express 4.x里分离了不少核心的中间件,我认为还应该更进一步。也许我们根本不需要一个框架。也许我们只需要一些在一起工作良好的模块就行。
希望现在你对于绕过Express视图渲染的好处已经明了于心了,如果你想看看具体的示例,可以看这里的示例程序。这里还有更大型的天气示例应用可供参考,希望能对你有所帮助。
(source:StrongLoop Blog, author:Patrick Steele-Idem)
为什么你应该抛弃Express的视图渲染引擎的更多相关文章
- Rails :布局和视图渲染
原文地址: http://guides.ruby-china.org/layouts_and_rendering.html Rails 布局和视图渲染 本文介绍 Action Controller 和 ...
- angularjs如何在视图渲染结束之后,或者render之后执行指令中的link方法呢?
angularjs如何在视图渲染结束之后,或者render之后执行指令中的link方法 关键字: $timeout app.directive("myDirective",func ...
- SpringMVC核心——视图渲染(包含视图解析)问题
一.本来想说的是返回值处理问题,但在 SpringMVC 中,返回值处理问题的核心就是视图渲染.所以这里标题叫视图渲染问题. 本来想在上一篇文章中对视图解析进行说明的,但是通过源码发现,它应该算到视图 ...
- spring mvc DispatcherServlet详解之四---视图渲染过程
整个spring mvc的架构如下图所示: 现在来讲解DispatcherServletDispatcherServlet的最后一步:视图渲染.视图渲染的过程是在获取到ModelAndView后的过程 ...
- 微信小程序教学第四章第二节(含视频):小程序中级实战教程:详情-视图渲染
§ 详情 - 数据渲染 本文配套视频地址: https://v.qq.com/x/page/x055550lrvd.html 开始前请把 ch4-2 分支中的 code/ 目录导入微信开发工具 这一节 ...
- thinkphp3.2笔记(3)视图渲染 模板的赋值与显示 系统变量
一 视图 1 视图渲染 渲染模板输出最常用的是使用display方法,调用格式:display('[模板文件]'[,'字符编码'][,'输出类型'])模板文件的写法支持下面几种:用法 描述不带任何 ...
- ZendFramework-2.4 源代码 - 关于MVC - View层 - 视图渲染器、视图插件管理器
<?php // 1. 视图渲染器 class PhpRenderer implements Renderer, TreeRendererInterface { /** * 插件管理器 */ p ...
- Vue视图渲染原理解析,从构建VNode到生成真实节点树
前言 在 Vue 核心中除了响应式原理外,视图渲染也是重中之重.我们都知道每次更新数据,都会走视图渲染的逻辑,而这当中牵扯的逻辑也是十分繁琐. 本文主要解析的是初始化视图渲染流程,你将会了解到从挂载组 ...
- Express ( MiddleWare/中间件 路由 在 Express 中使用模板引擎 常用API
A fast, un-opinionated, minimalist web framework for Node.js applications. In general, prefer simply ...
随机推荐
- java-9 异常处理
1.异常处理的基础知识 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error:如 ...
- 一个想法(续六):IT联盟创业计划:如何进行找钱、寻人、做事?
前言: 不知时间过的快,或是慢,一周过去了,在这一周,基本是围绕着创业这事在折腾. 也在看一些和创业相关的文章,不小心就看到了dudu在2007年呼喊创业的口号. 于是把他那一年的博文都给看了: 看着 ...
- Centos更换yum源
Centos更换yum源 步骤如下: 备份原始源 cd /etc/yum.repos.d/ mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/ ...
- Redis服务器搭建
下载并解压redis,然后进入redis所在目录 编译安装 make && make install 启动redis 服务 (加上&表示在后台运 ...
- noi 1.8 11图像旋转
水题不解释 其实我偷懒了 直接输出,,,,,,, 个人QQ:757394026团队QQ:466373640个人博客:www.doubleq.winc++/noi/信息学奥数博客:http://www. ...
- ASP.NET通用权限框架 权限管理系统源码jquery 精美UI源码
软件技术开发,合作请联系QQ:858-048-581 开发工具 VS2010 .sql2005.2008等(在Sql server数据执行脚本即可) VS2010 打开保证本地运行成功(数据库.源代 ...
- 旺财速啃H5框架之Bootstrap(六)
年后太忙,一直没有更新 好,这篇结束,速啃嘛,就应该拿重点,实用点.继续之前的内容,接着来讲讲网页中常用的布局组件与插件,我喜欢用简单的直接的话或案例来说明,就是针对那些想快速能做出点东西的人而准备的 ...
- AR入门系列-03-在unity中将调试好的Vuforia项目导出为APK
先设置build settings 选中Android后点击Player Settings Product Name设置安装后的Android程序的名字 Bundle Identifier 设置apk ...
- lua和c的亲密接触
介绍 lua和c的亲密接触,靠的是一个虚拟栈.lua通过这个虚拟栈来实现和c之间值的互传.栈上的每一个元素是一个lua值(nil,number,string...). 当lua调用c函数的时候,这个函 ...
- 团队项目中js冲突
在我们平时的项目中,特别是模块化开发中.经常会遇到一些js冲突问题,特别是当项目复杂到一定程度的时候.比如项目中引入了相当多的类库,框架以后. 那么.我们如何去避免或者解决这类问题呢? 1.避免使用全 ...