zoukankan      html  css  js  c++  java
  • .3-浅析express源码之applicaiton模块(2)-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).__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函数的过程解析完毕。

  • 相关阅读:
    "饼状图/环形图/玫瑰图"组件:<chart-pie> —— 快应用组件库H-UI
    "柱状图"组件:<chart-bar> —— 快应用组件库H-UI
    "折线图"组件:<chart-line> —— 快应用组件库H-UI
    "进度条"组件:<chart-progress> —— 快应用组件库H-UI
    "警告提示"组件:<alert> —— 快应用组件库H-UI
    "全局提示"组件:<message> —— 快应用组件库H-UI
    "滑动操作"组件:<swipe-action> —— 快应用组件库H-UI
    Git使用总结之修改了用户名之后git无法使用
    Facebook授权登录
    Android键盘显示和隐藏
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8855524.html
Copyright © 2011-2022 走看看