本小节是第六次迭代,主要的目的是介绍一下 Express 是如何集成现有的渲染引擎的。与渲染引擎有关的事情涉及到下面几个方面:
Express 通过 app.engine(ext, callback)
方法即可创建一个你自己的模板引擎。其中,ext
指的是文件扩展名、callback
是模板引擎的主函数,接受文件路径、参数对象和回调函数作为其参数。
//下面的代码演示的是一个非常简单的能够渲染 “.ntl” 文件的模板引擎。
var fs = require('fs'); // 此模板引擎依赖 fs 模块
app.engine('ntl', function (filePath, options, callback) { // 定义模板引擎
fs.readFile(filePath, function (err, content) {
if (err) return callback(new Error(err));
// 这是一个功能极其简单的模板引擎
var rendered = content.toString().replace('#title#', '')
.replace('#message#', '<h1>'+ options.message +'</h1>');
return callback(null, rendered);
})
});
为了让应用程序可以渲染模板文件,还需要做如下设置:
//views, 放模板文件的目录
app.set('views', './views')
//view engine, 模板引擎
app.set('view engine', 'ntl')
一旦 view engine
设置成功,就不需要显式指定引擎,或者在应用中加载模板引擎模块,Express 已经在内部加载。下面是如何渲染页面的方法:
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
});
要想实现上述功能,首先在 Application 类中定义两个变量,一个存储 app.set
和 app.get
这两个方法存储的值,另一个存储模板引擎中扩展名和渲染函数的对应关系。
然后是实现 app.set
函数:
Application.prototype.set = function(setting, val) {
if (arguments.length === 1) {
// app.get(setting)
return this.settings[setting];
}
this.settings[setting] = val;
return this;
};
代码中不仅仅实现了设置,如何传入的参数只有一个等价于 get
函数。
接着实现 app.get
函数。因为现在已经有了一个 app.get
方法用来设置路由,所以需要在该方法上进行重载。
http.METHODS.forEach(function(method) {
method = method.toLowerCase();
Application.prototype[method] = function(path, fn) {
if(method === 'get' && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}
...
};
});
最后实现 app.engine
进行扩展名和引擎函数的映射。
Application.prototype.engine = function(ext, fn) {
// get file extension
var extension = ext[0] !== '.'
? '.' + ext
: ext;
// store engine
this.engines[extension] = fn;
return this;
};
扩展名当做 key,统一添加 “.”。到此设置模板引擎相关信息的函数算是完成,接下来就是最重要的渲染引擎函数的实现。
res.render = function(view, options, callback) {
var app = this.req.app;
var done = callback;
var opts = options || {};
var self = this;
//如果定义回调,则返回,否则渲染
done = done || function(err, str) {
if(err) {
return self.req.next(err);
}
self.send(str);
};
//渲染
app.render(view, opts, done);
};
渲染函数一共有三个参数,view
表示模板的名称,options
是模板渲染的变量,callback
是渲染成功后的回调函数。函数内部直接调用 render
函数进行渲染,渲染完成后调用 done
回调。这里有两个地方需要注意下,第一个是 this.req.app
变量,另一个是 self.req.next
函数,二者目前都没有实现。
这里先定义 req.app
变量,这个变量初始化需要 application 对象,方法很多,这里使用最简单的方法,直接在 expressInit
中赋值:
exports.init = function (app) {
return function expressInit(req, res, next) {
//request文件可能用到res对象
req.res = res;
//response文件可能用到req对象
res.req = req;
//赋值
req.app = app;
//修改原始req和res原型
Object.setPrototypeOf(req, request);
Object.setPrototypeOf(res, response);
//继续
next();
};
};
然后修改 Application.prototype.lazyrouter
函数,传入 app 变量:
Application.prototype.lazyrouter = function () {
if (!this._router) {
this._router = new Router();
this._router.use(middleware.init(this));
}
};
接着定义 req.next
变量:
//如果定义回调,则返回,否则渲染
done = done || function (err, str) {
if (err) {
return self.req.next(err);
}
self.send(str);
};
req.next
函数默认是没有定义的,这里需要赋值一下,在 Router.handle
函数中,可以保存 next
函数:
proto.handle = function(req, res, done) {
//...
//保存原始路径
req.orginalUrl = req.orginalUrl || req.url;
// setup next layer
req.next = next;
//....
}
接下来创建一个 view.js
文件,主要功能是负责各种模板引擎和框架间的隔离,保持对内接口的统一性。
function View(name, options) {
var opts = options || {};
this.defaultEngine = opts.defaultEngine;
this.root = opts.root;
this.ext = path.extname(name);
this.name = name;
var fileName = name;
if (!this.ext) {
// get extension from default engine name
this.ext = this.defaultEngine[0] !== '.'
? '.' + this.defaultEngine
: this.defaultEngine;
fileName += this.ext;
}
// store loaded engine
this.engine = opts.engines[this.ext];
// lookup path
this.path = this.lookup(fileName);
}
View 类内部定义了很多属性,主要包括引擎、根目录、扩展名、文件名等等,为了以后的渲染做准备。
View.prototype.render = function render(options, callback) {
this.engine(this.path, options, callback);
};
View 的渲染函数内部就是调用一开始注册的引擎渲染函数。了解了 View 的定义,接下来实现 app.render
模板渲染函数。
Application.prototype.render = function(name, options, callback) {
var done = callback;
var engines = this.engines;
var opts = options;
view = new View(name, {
defaultEngine: this.get('view engine'),
root: this.get('views'),
engines: engines
});
if (!view.path) {
var err = new Error('Failed to lookup view "' + name + '"');
return done(err);
}
try {
view.render(options, callback);
} catch (e) {
callback(e);
}
};
view.js
文件还有一些细节没有在教程中展示出来,可以参考 github 上传的案例代码。
运行 node test/index.js
,查看效果。
上面的代码是自己注册的引擎,如果想要和现有的模板引擎结合还需要在回调函数中引用模板自身的渲染方法,当然为了方便,Express 框架内部提供了一个默认方法,如果模板引擎导出了该方法,则表示该模板引擎支持 Express 框架,无需使用 app.engine
再次封装。
该方法声明如下:
__express(filePath, options, callback)
可以参考 ejs 模板引擎的代码,看看它们是如何写的:
//该行代码在lib/ejs.js文件的355行左右
exports.__express = exports.renderFile;
Express 框架是如何实现这个默认加载的功能的呢?很简单,只需要在 View 的构造函数中加一个判断即可。
if (!opts.engines[this.ext]) {
// load engine
var mod = this.ext.substr(1);
opts.engines[this.ext] = require(mod).__express;
}
代码逻辑很简单,如果没有找到引擎对应的渲染函数,那就尝试加载 __express 函数。
大佬,给点反馈?
平均评分 / 5. 投票数:
很抱歉,这篇文章不能帮助到你
请让我们改进这篇文章
告诉我们我们如何改善这篇文章?