zoukankan      html  css  js  c++  java
  • artTemplate模板引擎的源码拜读

    最初接触的模板引擎还是基于node的ejs,当时觉得很神奇原来还可以这么玩,后来随着学习的深入,使用过jade,doT等,当然还有一些比较火的诸如juicer、underscore还没有深入接触,直到今年上半年由于项目需要就想着要不试试腾讯的artTemplate,感觉牛逼也吹的挺响的。开始了解后,觉得它比我之前使用过的jade、doT都好用,调试神马的也方便很多,采用预编译的方式也让性能非常优越。

    其实看了源码后简单的总结出来就是这么一句话:就是先获取html中对应的id下得innerHTML,利用开始标签和关闭标签进行字符串切分,其实是将模板划分成两部份内容,一部分是html部分,一部分是逻辑部分,通过区别一些特殊符号比如each、if等来将字符串拼接成函数式的字符串,将两部分各自经过处理后,再次拼接到一起,最后将拼接好的字符串采用new Function()的方式转化成所需要的函数。

    总共700多行的代码,其实简化下来就200多行代码,

    刚开始肯定是先定义方法:

     1 var template = function (filename, content) {
     2     return typeof content === 'string'
     3     ?   compile(content, {
     4             filename: filename
     5         })
     6     :   renderFile(filename, content);
     7 };
     8 
     9 
    10 var renderFile = template.renderFile = function (filename, data) {
    11     var fn = template.get(filename) || showDebugInfo({
    12         filename: filename,
    13         name: 'Render Error',
    14         message: 'Template not found'
    15     });
    16     return data ? fn(data) : fn;
    17 };

    定义好后开始缓存fn方法,通过id获取模板内容

    template.get = function (filename) {
    
        var cache;
    
        if (cacheStore[filename]) {
            // 使用内存缓存
            cache = cacheStore[filename];
        } else if (typeof document === 'object') {
            // 加载模板并编译
            var elem = document.getElementById(filename);
    
            if (elem) {
                var source = (elem.value || elem.innerHTML)
                .replace(/^s*|s*$/g, '');
                cache = compile(source, {
                    filename: filename
                });
            }
        }
    
        return cache;
    };

    其实是对渲染的方法进行缓存,接下来开始编译模板

    var compile = template.compile = function (source, options) {
    
        // 合并默认配置
        options = options || {};
        for (var name in defaults) {
            if (options[name] === undefined) {
                options[name] = defaults[name];
            }
        }
    
        var filename = options.filename;
    
        try {
    
            var Render = compiler(source, options);
    
        } catch (e) {
    
            e.filename = filename || 'anonymous';
            e.name = 'Syntax Error';
    
            return showDebugInfo(e);
    
        }
    
        return render;
    
    };

    把从模板中提取的代码分成两部分,一部分是html,一部分是逻辑部分,各自进行处理

    function compiler (source, options) {
    
        var debug = options.debug;
        var openTag = options.openTag;
        var closeTag = options.closeTag;
        var parser = options.parser;
        var compress = options.compress;
        var escape = options.escape;
    
    
        var headerCode = "'use strict';"
        + "var $utils=this,$helpers=$utils.$helpers,"
        + (debug ? "$line=0," : "");
    
        var mainCode = replaces[0];
    
        var footerCode = "return new String(" + replaces[3] + ");"
    
        // html与逻辑语法分离
        forEach(source.split(openTag), function (code) {
            code = code.split(closeTag);
    
            var $0 = code[0];
            var $1 = code[1];
    
            // code: [html]
            if (code.length === 1) {
    
                mainCode += html($0);
    
            // code: [logic, html]
            } else {
    
                mainCode += logic($0);
    
                if ($1) {
                    mainCode += html($1);
                }
            }
    
    
        });
    
        var code = headerCode + mainCode + footerCode;
    
    
        try {
    
            //将拼接好的字符串通过new Function的方法进行函数化
            var Render = new Function("$data", "$filename", code);
            Render.prototype = utils;
    
            return Render;
    
        } catch (e) {
            e.temp = "function anonymous($data,$filename) {" + code + "}";
            throw e;
        }
    
    
    
    
        // 处理 HTML 语句
        function html (code) {
    
            // 记录行号
            line += code.split(/
    /).length - 1;
    
            // 压缩多余空白与注释
            if (compress) {
                code = code
                .replace(/s+/g, ' ')
                .replace(/<!--[wW]*?-->/g, '');
            }
    
            if (code) {
                code = replaces[1] + stringify(code) + replaces[2] + "
    ";
            }
    
            return code;
        }
    
    
        // 处理逻辑语句
        function logic (code) {
    
            var thisLine = line;
    
            if (parser) {
    
                 // 语法转换插件钩子
                code = parser(code, options);
    
            } else if (debug) {
    
                // 记录行号
                code = code.replace(/
    /g, function () {
                    line ++;
                    return "$line=" + line +  ";";
                });
    
            }
    
    
            // 输出语句. 编码: <%=value%> 不编码:<%=#value%>
            if (code.indexOf('=') === 0) {
    
                var escapeSyntax = escape && !/^=[=#]/.test(code);
    
                code = code.replace(/^=[=#]?|[s;]*$/g, '');
    
                // 对内容编码
                if (escapeSyntax) {
    
                    var name = code.replace(/s*([^)]+)/, '');
    
                    // 排除 utils.* | include | print
    
                    if (!utils[name] && !/^(include|print)$/.test(name)) {
                        code = "$escape(" + code + ")";
                    }
    
                // 不编码
                } else {
                    code = "$string(" + code + ")";
                }
    
    
                code = replaces[1] + code + replaces[2];
    
            }
    
    
            // 提取模板中的变量名
            forEach(getVariable(code), function (name) {
    
                // name 值可能为空,在安卓低版本浏览器下
                if (!name || uniq[name]) {
                    return;
                }
    
                var value;
    
                // 声明模板变量
                // 赋值优先级:
                // [include, print] > utils > helpers > data
                if (name === 'print') {
    
                    value = print;
    
                } else if (name === 'include') {
    
                    value = include;
    
                } else if (utils[name]) {
    
                    value = "$utils." + name;
    
                } else if (helpers[name]) {
    
                    value = "$helpers." + name;
    
                } else {
    
                    value = "$data." + name;
                }
    
                headerCode += name + "=" + value + ",";
                uniq[name] = true;
    
    
            });
    
            return code + "
    ";
        }
    
    
    };

    在进行逻辑部分处理时,静态分析模板变量;采用正则表达式首先过滤掉系统关键字,其次过滤掉不合法变量,再去除掉收尾的都好,最后根据都好分割成数组形式,以此来拼接字符串。再通过上面的new Function将字符串生成一个function,此为渲染方法,提供数据,进行剩下的结合。

    var KEYWORDS =
        // 关键字
        'break,case,catch,continue,debugger,default,delete,do,else,false'
        + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
        + ',throw,true,try,typeof,var,void,while,with'
    
        // 保留字
        + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
        + ',final,float,goto,implements,import,int,interface,long,native'
        + ',package,private,protected,public,short,static,super,synchronized'
        + ',throws,transient,volatile'
    
        // ECMA 5 - use strict
        + ',arguments,let,yield'
    
        + ',undefined';
    
    var REMOVE_RE = //*[wW]*?*/|//[^
    ]*
    |//[^
    ]*$|"(?:[^"\]|\[wW])*"|'(?:[^'\]|\[wW])*'|s*.s*[$w.]+/g;
    var SPLIT_RE = /[^w$]+/g;//非数字字母下滑线和$符以外的其他字符
    var KEYWORDS_RE = new RegExp(["\b" + KEYWORDS.replace(/,/g, '\b|\b') + "\b"].join('|'), 'g');
    var NUMBER_RE = /^d[^,]*|,d[^,]*/g;//匹配数字开头或者逗号后紧跟着数字的
    var BOUNDARY_RE = /^,+|,+$/g;//匹配开头的一个或多个逗号以及结尾的 用于去除首尾的逗号
    var SPLIT2_RE = /^$|,+/;//匹配多个逗号,用于分割 类似 param1,param2,,param3=> ["param1","param2","param3"] ,/^$/是为了匹配防止空字符串被切割
    
    
    // 获取变量
    function getVariable (code) {
        return code
        .replace(REMOVE_RE, '')
        .replace(SPLIT_RE, ',')
        .replace(KEYWORDS_RE, '')
        .replace(NUMBER_RE, '')
        .replace(BOUNDARY_RE, '')
        .split(SPLIT2_RE);
    };
    
    
    // 字符串转义
    function stringify (code) {
        return "'" + code
        // 单引号与反斜杠转义
        .replace(/('|\)/g, '\$1')
        // 换行符转义(windows + linux)
        .replace(/
    /g, '\r')
        .replace(/
    /g, '\n') + "'";
    }

    还有一些模板的辅助方法、错误事件、模板调试器等等

    template.helper = function (name, helper) {
        helpers[name] = helper;
    };
    
    var helpers = template.helpers = utils.$helpers;
    
    
    /**
     * 模板错误事件(可由外部重写此方法)
     * @name    template.onerror
     * @event
     */
    template.onerror = function (e) {
        var message = 'Template Error
    
    ';
        for (var name in e) {
            message += '<' + name + '>
    ' + e[name] + '
    
    ';
        }
    
        if (typeof console === 'object') {
            console.error(message);
        }
    };
    
    
    // 模板调试器
    var showDebugInfo = function (e) {
    
        template.onerror(e);
    
        return function () {
            return '{Template Error}';
        };
    };

    最后return template;

    // RequireJS && SeaJS
    if (typeof define === 'function') {
        define(function() {
            return template;
        });
    
    // NodeJS
    } else if (typeof exports !== 'undefined') {
        module.exports = template;
    } else {
        this.template = template;
    }

    从网上找了一个artTemplate的结构图,刚开始看可能看不明白,等看完源码再看这张图,会感觉清晰很多。image

  • 相关阅读:
    pip install selenium==版本号 报错
    解决phantomjs输出中文乱码
    phantomjs学习之网页访问测速
    phantomjs学习之截图
    bzoj1069-最大土地面积
    hdu4372-Count the Buildings
    bzoj3786-星系探索
    Codeforces633H-Fibonacci-ish II
    hdu3625-Rooms
    斯特林数
  • 原文地址:https://www.cnblogs.com/yinsu12311/p/5816259.html
Copyright © 2011-2022 走看看