深入源码
首先, 看下 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 可参考这里
来源: https://www.cnblogs.com/chyingp/p/express-render-engine.html