深入源码

首先,看下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);
};

源码开头有 cacheengines 两个属性,它们在 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');

有两个关于模版引擎的配置:

  1. views:模版文件放在哪里,默认是在项目根目录下。举个例子:app.set('views', './views')
  2. view engine:使用什么模版引擎,举例:app.set('view engine', 'jade')

可以看到,默认是用jade做模版的。如果不想用jade怎么办呢?下面会提供一些模板引擎选择的思路。

选择标准

需要考虑两点:实际业务需求、个人偏好。

首先考虑业务需求,需要支持以下几点特性。

  • 支持模版继承(extend)
  • 支持模版扩展(block)
  • 支持模版组合(include)
  • 支持预编译

对比了下,jadenunjunks都满足要求。个人更习惯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 的值 ,进行如下操作

  1. 确定模板的路径
  2. 根据模板的扩展性确定采用哪个渲染引擎
  3. 加载渲染引擎

重复调用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就会根据扩展名加载相应的模板引擎。比如:

  1. index.jade:加载引擎jade
  2. 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:模板引擎深入研究的更多相关文章

  1. Django模板引擎的研究

    Django模板引擎的研究 原创博文,转载请注明出处. 以前曾遇到过错误Reverse for ‘*’ with arguments '()' and keyword arguments' not f ...

  2. 关于Django模板引擎的研究

    原创博文,转载请注明出处. 以前曾遇到过错误Reverse for ‘*’ with arguments '()' and keyword arguments' not found.1其原因是没有给视 ...

  3. express+模板引擎构建项目时遇到的几个小问题

    1.启动项目/调试项目 项目启动用:npm start 由于每次更改路由代码后必须重启服务才可以看效果,所以为了达到热加载的效果我们安装 supervisor:全局安装也可以: npm install ...

  4. 2 ~ express ~ 模板引擎的配置与使用

    一,创建应用 (一),创建应用,监听端口 var express = require('express') // 创建app应用 var app = express() app.listen(3000 ...

  5. express搭建web服务器、路由、get、post请求、multer上传文件、EJS模板引擎的使用

    express官网 postman工具下载地址  multer的npm文档地址 express模板引擎怎么使用  地址:http://www.expressjs.com.cn/guide/using- ...

  6. 前端笔记之NodeJS(三)Express&ejs模板引擎&请求识别

    一.Express框架 1.1基本使用 创建http服务器特别麻烦,express框架解决了这个的问题. Express在node界的地位,就相当于jQuery在DOM界的地位.jQuery的核心就是 ...

  7. Express开发实例(2) —— Jade模板引擎

    前一篇通过helloworld,简单介绍了Express中的开发,本篇继续深入的学习express的模板. 关于Jade的用法,网上有很多,本篇参考:Jade语法 安装相关模块 在实验代码前,应该先安 ...

  8. express-9 Handlebars模板引擎(2)

    视图和布局 视图通常表现为网站上的各个页面(它也可以表现为页面中AJAX局部加载的内容,或一封电子邮件,或页面上的任何东西).默认情况下,Express会在views子目录中查找视图.布局是一种特殊的 ...

  9. express-8 Handlebars模板引擎(1)

    简介 使用JavaScript生成一些HTML document.write('<h1>Please Don\'t Do This</h1>'); document.write ...

随机推荐

  1. [uiautomator篇] UiWatcher的使用

    //package com.softwinner.pad.mark3d; package com.softwinner.performance.benchmark.mark3d; import and ...

  2. CodeForces contest/776 A+B+C题解

    ICM Technex 2017 and Codeforces Round #400 (Div. 1 +Div.2,combined) A. A Serial Killer 谜一样的题意:每天从两个人 ...

  3. Terracotta2

    Terracotta 3.2.1简介 (二) Terracotta分布式缓存EhcacheQuartzTerracotta的web session方案  高效.高可用的Web Session解决方案 ...

  4. 安卓手机 HTML5 手机页面 输入表单被键盘遮挡住了

    TML5 手机页面 输入表单被键盘遮挡住了 请问 大神 怎么 js 或者 JQ 判断安卓手机软键盘的键盘隐藏键按下去了? 有使用 uexWindow 方法 能判断到确定键 是 13 但是不知道这个键的 ...

  5. [luoguP3413] SAC#1 - 萌数(数位DP)

    传送门 gtm的数位dp! 看到好多题解,都是记忆化搜索,好像非常方便啊,但是我还是用递推好了,毕竟还是有些类似数位dp的题用递推的思路,记忆化做不了,现在多培养一下思路 首先这道题, 只看长度大于等 ...

  6. 使用Jackson在Java中处理JSON

    在工作中实际使用到Java处理JSON的情况,且有很大部分都使用的是开源工具Jackson实现的. 一.入门 Jackson中有个ObjectMapper类很是实用,用于Java对象与JSON的互换. ...

  7. CSGO

    CSGO Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others) Problem D ...

  8. ⑨要写信(codevs 1697)

    题目描述 Description 琪露诺(冰之妖精)有操控冷气的能力.能瞬间冻结小东西,比普通的妖精更危险.一直在释放冷气的她周围总是非常寒冷. 由于以下三点原因…… 琪露诺的符卡 冰符“Icicle ...

  9. Laravel 报500错误

    Laravel报500错误 发生情境: 使用Composer安装Laravel5.1版本到本地wamp环境,可以成功访问框架首页,然后上传到服务器上,报500错误. 解决: (1)在首页public/ ...

  10. tmux基本操作

    安装和移除: // 安装 sudo apt-get install tmux // 移除 sudo apt-get remove tmux 常用命令: tmux [new -s 会话名 -n 窗口名] ...