標籤:name 需要 base amp == try 分割 define 擷取檔案
這個模組還漏了一個稍微複雜點的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).__express if (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的第二個參數返回。
至此,render函數的過程解析完畢。
.2-淺析express源碼之applicaiton模組-app.render(2)