1. 程式人生 > >.2-淺析express源碼之applicaiton模塊-app.render(2)

.2-淺析express源碼之applicaiton模塊-app.render(2)

define dir express failed 是否 name 指定 字符串 ons

  這個模塊還漏了一個稍微復雜點的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)