.2-浅析express源码之applicaiton模块-app.render

愿君学长松,慎勿作桃李。这篇文章主要讲述.2-浅析express源码之applicaiton模块-app.render相关的知识,希望能为你提供帮助。
这个模块还漏了一个稍微复杂点的API,就是app.render,首先看官网的定义:
app.render(view, [locals], callback)
view为对应的文件名,locals为一个配置对象,callback为解析完成的回调函数。
涉及到的全局属性有
view:默认为一个内置模块,负责解析文件路径与获取对应文件后缀的parser
views:默认为process() + ‘/views‘,一个字符串或数组,搜索对应文件路径时的目录
view engine:默认解析引擎,需要自定义
另外,locals配置对象既可以提供render相关的设置,也可以提供渲染模板所需要的参数。
 
app.render
首先看一眼render主函数:

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; // 参数修正 if (typeof options === ‘function‘) { done = options; opts = {}; }// 参数合并 merge(renderOptions, this.locals); if (opts._locals) { merge(renderOptions, opts._locals); } merge(renderOptions, opts); // 设置缓存 if (renderOptions.cache == null) { renderOptions.cache = this.enabled(‘view cache‘); }// 尝试获取缓存 if (renderOptions.cache) { view = cache[name]; }// 尝试获取文件绝对路径 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); }// 设置缓存 if (renderOptions.cache) { cache[name] = view; } }// 渲染 tryRender(view, renderOptions, done); };

结构直接清晰,稍微说一下。
1、第二个配置对象参数是可选的
2、若未定义cache属性,是否缓存保持与全局缓存属性一致
3、view属性基本上不需要自己定义,因为看起来挺麻烦的
4、最后的tryRender方法来源于view属性的原型方法,可能为了拓展才分割出来
function tryRender(view, options, callback) { try { view.render(options, callback); } catch (err) { callback(err); } }


这里以express-generator的demo来说明一下,在生成的目录中,app.js涉及的相关代码如下:
app.set(‘views‘, path.join(__dirname, ‘views‘)); app.set(‘view engine‘, ‘jade‘);

设置了默认解析引擎为jade,默认文件目录为views文件夹。
然后假设调用代码如下:
app.render(‘index‘, { title: ‘Express‘ }, callback);

先不管callback是什么,进入内置view模块。
function View(name, options) { var opts = options || {}; // 获取参数 this.defaultEngine = opts.defaultEngine; this.ext = extname(name); this.name = name; this.root = opts.root; /** * 有两种方式指定文件后缀 * 1.name参数提供完整的文件名+后缀 * 2.提前设置默认解析引擎view engine参数 */ if (!this.ext & & !this.defaultEngine) { throw new Error(‘No default engine was specified and no extension was provided.‘); }var fileName = name; /** * 文件无后缀时会拼接默认解析引擎与文件名 */ if (!this.ext) { // jade + . => .jade this.ext = this.defaultEngine[0] !== ‘.‘ ? ‘.‘ + this.defaultEngine : this.defaultEngine; // index + .jade => index.jade fileName += this.ext; }// 无对应引擎模块时 if (!opts.engines[this.ext]) { // 获取后缀 var mod = this.ext.substr(1) debug(‘require "%s"‘, mod)/** * 引进对应模块 * 比如jade => fn = requore(‘jade‘).__express */ var fn = require(mod).__expressif (typeof fn !== ‘function‘) { throw new Error(‘Module "‘ + mod + ‘" does not provide a view engine.‘) }opts.engines[this.ext] = fn }// 将引擎解析模块存入本地属性 this.engine = opts.engines[this.ext]; // 搜索路径 this.path = this.lookup(fileName); }

基本上信息都写在注释里了,稍微提一下,最佳的实践就是在文件名直接给出对应的后缀,并且提前在全局属性设置并引入解析引擎,这样在生成对应的view时会省去很多的时间。
完成文件名拼接与解析模块引入后,会进行文件的路径搜素,由于设置了指定目录,所以这一步也就很简单了,源码如下:
View.prototype.lookup = function lookup(name) { var path; // 还特地跑去查了一下 这个方法接受字符串 老了…… var roots = [].concat(this.root); debug(‘lookup "%s"‘, name); // 遍历所有的本地目录 for (var i = 0; i < roots.length & & !path; i++) { var root = roots[i]; // 拼接文件名与目录 var loc = resolve(root, name); var dir = dirname(loc); var file = basename(loc); path = this.resolve(dir, file); }return path; };

这个就太简单了。

以jade为例,可以稍微看一眼解析入口函数:
exports.render = function(str, options, fn) { // 参数修正 if (‘function‘ == typeof options) { fn = options, options = undefined; } if (typeof fn === ‘function‘) { var res try { // 解析文件 res = exports.render(str, options); } catch (ex) { return fn(ex); } // 调用callback 第二参数为解析后的字符串 return fn(null, res); }options = options || {}; // cache requires .filename if (options.cache & & !options.filename) { throw new Error(‘the "filename" option is required for caching‘); } // parse... return handleTemplateCache(options, str)(options); };

总的来说,就是根据文件的绝对路径、参数对象返回一个解析后的html字符串,作为callback的第二个参数返回。
【.2-浅析express源码之applicaiton模块-app.render】至此,render函数的过程解析完毕。

    推荐阅读