Express:模板引擎深入研究
深入源码
首先,看下express模板默认配置。
- view:模板引擎模块,对应 require('./view'),结合 res.render(name) 更好了解些。下面会看下 view 模块。
- views:模板路径,默认在 views 目录下。
// default configuration
this.set('view', View);
this.set('views', resolve('views'));
腾讯IVWEB前端团队招前端工程师,2年以上工作经验,本科以上学历,有意者可私信、留言,或者邮箱联系 2377488447@qq.com,JD可参考这里
从实例出发
从官方脚手架生成的代码出发,模板配置如下:
- views:模板文件在 views 目录下;
- view engine:用jade这个模板引擎进行模板渲染;
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
假设此时有如下代码调用,内部逻辑是如何实现的?
res.render('index');
res.render(view)
完整的 render
方法代码如下:
/**
* Render `view` with the given `options` and optional callback `fn`.
* When a callback function is given a response will _not_ be made
* automatically, otherwise a response of _200_ and _text/html_ is given.
*
* Options:
*
* - `cache` boolean hinting to the engine it should cache
* - `filename` filename of the view being rendered
*
* @public
*/
res.render = function render(view, options, callback) {
var app = this.req.app;
var done = callback;
var opts = options || {};
var req = this.req;
var self = this;
// support callback function as second arg
if (typeof options === 'function') {
done = options;
opts = {};
}
// merge res.locals
opts._locals = self.locals;
// default callback to respond
done = done || function (err, str) {
if (err) return req.next(err);
self.send(str);
};
// render
app.render(view, opts, done);
};
核心代码就一句,调用了 app.render(view) 这个方法。
res.render = function (name, options, callback) {
var app = this.req.app;
app.render(view, opts, done);
};
app.render(view)
完整源码如下:
/**
* Render the given view `name` name with `options`
* and a callback accepting an error and the
* rendered template string.
*
* Example:
*
* app.render('email', { name: 'Tobi' }, function(err, html){
* // ...
* })
*
* @param {String} name
* @param {String|Function} options or fn
* @param {Function} callback
* @public
*/
app.render = function render(name, options, callback) {
var cache = this.cache;
var done = callback;
var engines = this.engines;
var opts = options;
var renderOptions = {};
var view;
// support callback function as second arg
if (typeof options === 'function') {
done = options;
opts = {};
}
// merge app.locals
merge(renderOptions, this.locals);
// merge options._locals
if (opts._locals) {
merge(renderOptions, opts._locals);
}
// merge options
merge(renderOptions, opts);
// set .cache unless explicitly provided
if (renderOptions.cache == null) {
renderOptions.cache = this.enabled('view cache');
}
// primed cache
if (renderOptions.cache) {
view = cache[name];
}
// view
if (!view) {
var View = this.get('view');
view = new View(name, {
defaultEngine: this.get('view engine'),
root: this.get('views'),
engines: engines
});
if (!view.path) {
var dirs = Array.isArray(view.root) && view.root.length > 1
? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
: 'directory "' + view.root + '"'
var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
err.view = view;
return done(err);
}
// prime the cache
if (renderOptions.cache) {
cache[name] = view;
}
}
// render
tryRender(view, renderOptions, done);
};
源码开头有 cache
、engines
两个属性,它们在 app.int()
阶段就初始化了。
this.cache = {};
this.engines = {};
View模块源码
看下View模块的源码:
/**
* Initialize a new `View` with the given `name`.
*
* Options:
*
* - `defaultEngine` the default template engine name
* - `engines` template engine require() cache
* - `root` root path for view lookup
*
* @param {string} name
* @param {object} options
* @public
*/
function View(name, options) {
var opts = options || {};
this.defaultEngine = opts.defaultEngine;
this.ext = extname(name);
this.name = name;
this.root = opts.root;
if (!this.ext && !this.defaultEngine) {
throw new Error('No default engine was specified and no extension was provided.');
}
var fileName = name;
if (!this.ext) {
// get extension from default engine name
this.ext = this.defaultEngine[0] !== '.'
? '.' + this.defaultEngine
: this.defaultEngine;
fileName += this.ext;
}
if (!opts.engines[this.ext]) {
// load engine
opts.engines[this.ext] = require(this.ext.substr(1)).__express;
}
// store loaded engine
this.engine = opts.engines[this.ext];
// lookup path
this.path = this.lookup(fileName);
}
核心概念:模板引擎
模板引擎大家不陌生了,关于express模板引擎的介绍可以参考官方文档。
下面主要讲下使用配置、选型等方面的内容。
可选的模版引擎
包括但不限于如下模板引擎
- jade
- ejs
- dust.js
- dot
- mustache
- handlerbar
- nunjunks
配置说明
先看代码。
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
有两个关于模版引擎的配置:
views
:模版文件放在哪里,默认是在项目根目录下。举个例子:app.set('views', './views')
view engine
:使用什么模版引擎,举例:app.set('view engine', 'jade')
可以看到,默认是用jade
做模版的。如果不想用jade
怎么办呢?下面会提供一些模板引擎选择的思路。
选择标准
需要考虑两点:实际业务需求、个人偏好。
首先考虑业务需求,需要支持以下几点特性。
- 支持模版继承(extend)
- 支持模版扩展(block)
- 支持模版组合(include)
- 支持预编译
对比了下,jade
、nunjunks
都满足要求。个人更习惯nunjunks
的风格,于是敲定。那么,怎么样使用呢?
支持nunjucks
首先,安装依赖
npm install --save nunjucks
然后,添加如下配置
var nunjucks = require('nunjucks');
nunjucks.configure('views', {
autoescape: true,
express: app
});
app.set('view engine', 'html');
看下views/layout.html
<!DOCTYPE html>
<html>
<head>
<title>
{% block title %}
layout title
{% endblock %}
</title>
</head>
<body>
<h1>
{% block appTitle %}
layout app title
{% endblock %}
</h1>
<p>正文</p>
</body>
</html>
看下views/index.html
{% extends "layout.html" %}
{% block title %}首页{% endblock %}
{% block appTitle %}首页{% endblock %}
开发模板引擎
通过app.engine(engineExt, engineFunc)
来注册模板引擎。其中
- engineExt:模板文件后缀名。比如
jade
。 - engineFunc:模板引擎核心逻辑的定义,一个带三个参数的函数(如下)
// filepath: 模板文件的路径
// options:渲染模板所用的参数
// callback:渲染完成回调
app.engine(engineExt, function(filepath, options, callback){
// 参数一:渲染过程的错误,如成功,则为null
// 参数二:渲染出来的字符串
return callback(null, 'Hello World');
});
比如下面例子,注册模板引擎 + 修改配置一起,于是就可以愉快的使用后缀为tmpl
的模板引擎了。
app.engine('tmpl', function(filepath, options, callback){
// 参数一:渲染过程的错误,如成功,则为null
// 参数二:渲染出来的字符串
return callback(null, 'Hello World');
});
app.set('views', './views');
app.set('view engine', 'tmpl');
res.render(view [, locals] [, callback])
参数说明:
- view:模板的路径。
- locals:对象类型。渲染模板时传进去的本地变量。
- callback:回调函数。如果声明了的话,当渲染工作完成时被调用,参数为两个,分别是错误(如果出错的话)、渲染好的字符串。在这种情况下,response不会自动完成。当错误发生时,内部会自动调用 next(err)
view参数说明:
- 可以是相对路径(相对于
views
设置的目录),或者绝对路径; - 如果没有声明文件后缀,则以
view engine
设置为准; - 如果声明了文件后缀,那么Express会根据文件后缀,通过 require() 加载对应的模板引擎来完成渲染工作(通过模板引擎的 __express 方法完成渲染)。
locals参数说明:
locals.cache 启动模板缓存。在生产环境中,模板缓存是默认启用的。在开发环境,可以通过将 locals.cache 设置为true来启用模板缓存。
例子:
// send the rendered view to the client
res.render('index');
// if a callback is specified, the rendered HTML string has to be sent explicitly
res.render('index', function(err, html) {
res.send(html);
});
// pass a local variable to the view
res.render('user', { name: 'Tobi' }, function(err, html) {
// ...
});
关于view cache
The local variable cache enables view caching. Set it to true, to cache the view during development; view caching is enabled in production by default.
render(view, opt, callback) 这个方法调用时,Express会根据 view 的值 ,进行如下操作
- 确定模板的路径
- 根据模板的扩展性确定采用哪个渲染引擎
- 加载渲染引擎
重复调用render()方法,如果 cache === false
那么上面的步骤每次都会重新做一遍;如果 cache === true
,那么上面的步骤会跳过;
关键源代码:
if (renderOptions.cache) {
view = cache[name];
}
此外,在 view.render(options, callback)
里,options
也会作为参数传入this.engine(this.path, options, callback)
。也就是说,渲染引擎(比如jade
)也会读取到options.cache
这个配置。根据options.cache
的值,渲染引擎内部也可能会进行缓存操作。(比如为true时,jade
读取模板后会缓存起来,如果为false,每次都会重新从文件系统读取)
View.prototype.render = function render(options, callback) {
debug('render "%s"', this.path);
this.engine(this.path, options, callback);
};
备注:cache
配置对渲染引擎的影响是不确定的,因此实际需要用到某个渲染引擎时,需确保对渲染引擎足够了解。
以jade为例,在开发阶段,NODE_ENV !== 'production'
,cahce默认是false。因此每次都会从文件系统读取模板,再进行渲染。因此,在开发阶段,可以动态修改模板内容来查看效果。
当 NODE_ENV === 'production'
,cache 默认是true,此时会缓存模板,提升性能。
混合使用多种模板引擎
根据对源码的分析,实现很简单。只要带上文件扩展名,Express就会根据扩展名加载相应的模板引擎。比如:
index.jade
:加载引擎jade
index.ejs
:加载引擎ejss
// 混合使用多种模板引擎
var express = require('express');
var app = express();
app.get('/index.jade', function (req, res, next) {
res.render('index.jade', {title: 'jade'});
});
app.get('/index.ejs', function (req, res, next) {
res.render('index.ejs', {title: 'ejs'});
});
app.listen(3000);
同样的模板引擎,不同的文件扩展名
比如模板引擎是jade
,但是因为一些原因,扩展名需要采用.tpl
// 同样的模板引擎,不同的扩展名
var express = require('express');
var app = express();
// 模板采用 tpl 扩展名
app.set('view engine', 'tpl');
// 对于以 tpl 扩展名结尾的模板,采用 jade 引擎
app.engine('tpl', require('jade').__express);
app.get('/index', function (req, res, next) {
res.render('index', {title: 'tpl'});
});
app.listen(3000);
相关链接
Using template engines with Express
http://expressjs.com/en/guide/using-template-engines.html
res.render 方法使用说明
http://expressjs.com/en/4x/api.html#res.render
腾讯IVWEB前端团队招前端工程师,2年以上工作经验,本科以上学历,有意者可私信、留言,或者邮箱联系 2377488447@qq.com,JD可参考这里
Express:模板引擎深入研究的更多相关文章
- Django模板引擎的研究
Django模板引擎的研究 原创博文,转载请注明出处. 以前曾遇到过错误Reverse for ‘*’ with arguments '()' and keyword arguments' not f ...
- 关于Django模板引擎的研究
原创博文,转载请注明出处. 以前曾遇到过错误Reverse for ‘*’ with arguments '()' and keyword arguments' not found.1其原因是没有给视 ...
- express+模板引擎构建项目时遇到的几个小问题
1.启动项目/调试项目 项目启动用:npm start 由于每次更改路由代码后必须重启服务才可以看效果,所以为了达到热加载的效果我们安装 supervisor:全局安装也可以: npm install ...
- 2 ~ express ~ 模板引擎的配置与使用
一,创建应用 (一),创建应用,监听端口 var express = require('express') // 创建app应用 var app = express() app.listen(3000 ...
- express搭建web服务器、路由、get、post请求、multer上传文件、EJS模板引擎的使用
express官网 postman工具下载地址 multer的npm文档地址 express模板引擎怎么使用 地址:http://www.expressjs.com.cn/guide/using- ...
- 前端笔记之NodeJS(三)Express&ejs模板引擎&请求识别
一.Express框架 1.1基本使用 创建http服务器特别麻烦,express框架解决了这个的问题. Express在node界的地位,就相当于jQuery在DOM界的地位.jQuery的核心就是 ...
- Express开发实例(2) —— Jade模板引擎
前一篇通过helloworld,简单介绍了Express中的开发,本篇继续深入的学习express的模板. 关于Jade的用法,网上有很多,本篇参考:Jade语法 安装相关模块 在实验代码前,应该先安 ...
- express-9 Handlebars模板引擎(2)
视图和布局 视图通常表现为网站上的各个页面(它也可以表现为页面中AJAX局部加载的内容,或一封电子邮件,或页面上的任何东西).默认情况下,Express会在views子目录中查找视图.布局是一种特殊的 ...
- express-8 Handlebars模板引擎(1)
简介 使用JavaScript生成一些HTML document.write('<h1>Please Don\'t Do This</h1>'); document.write ...
随机推荐
- API错误码设计-资料
搜索到一篇文章:新浪微博API错误代码说明对照表 可以参考新浪微博的错误码设计思路,设计自己系统的错误码.
- hdu 2177 威佐夫博弈变形
取(2堆)石子游戏 Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- 九度oj 题目1016:火星A+B
题目描述: 读入两个不超过25位的火星正整数A和B,计算A+B.需要注意的是:在火星上,整数不是单一进制的,第n位的进制就是第n个素数.例如:地球上的10进制数2,在火星上记为“1,0”,因为 ...
- 刷题总结——谈笑风生(主席树+dfs序的应用)
题目: Description 设T 为一棵有根树,我们做如下的定义:• 设a和b为T 中的两个不同节点.如果a是b的祖先,那么称“a比b不知道高明到哪里去了”.• 设a 和 b 为 T 中的两个不同 ...
- 论文笔记:Ten years of pedestrian detection, what have we learned?
最近正在研究行人检测,学习了一篇2014年发表在ECCV上的一篇综述性的文章,是对行人检测过去十年的一个回顾,从dataset,main approaches的角度分析了近10年的40多篇论文提出的方 ...
- [TJOI2009]开关 (线段树)
题目描述 现有N(2 ≤ N ≤ 100000)盏灯排成一排,从左到右依次编号为:1,2,......,N.然后依次执行M(1 ≤ M ≤ 100000)项操作,操作分为两种:第一种操作指定一个区间[ ...
- Spoj-TRNGL Make Triangle
Make Triangle Chayanika loves Mathematics. She is learning a new chapter geometry. While reading the ...
- 济南学习 Day 5 T2 pm
逆欧拉函数(arc)题目描述:已知phi(N),求N.输入说明:两个正整数,分别表示phi(N)和K.输出说明:按升序输出满足条件的最小的K个N.样例输入:8 4杨丽输出:15 16 20 24数据范 ...
- java私有构造函数
1. 强调类的单例模式 public class Elvs { //公有的静态域,来说明该类只能有一个实例(实例化一次后,后面都是同一个实例) public static final Elvs INS ...
- POJ Blue Jeans [枚举+KMP]
传送门 F - Blue Jeans Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u ...