artTemplate模板引擎的源码拜读

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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(/\n/).length - 1;

        // 压缩多余空白与注释
        if (compress) {
            code = code
            .replace(/\s+/g, ‘ ‘)
            .replace(/<!--[\w\W]*?-->/g, ‘‘);
        }

        if (code) {
            code = replaces[1] + stringify(code) + replaces[2] + "\n";
        }

        return code;
    }


    // 处理逻辑语句
    function logic (code) {

        var thisLine = line;

        if (parser) {

             // 语法转换插件钩子
            code = parser(code, options);

        } else if (debug) {

            // 记录行号
            code = code.replace(/\n/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 + "\n";
    }


};

 

在进行逻辑部分处理时,静态分析模板变量;采用正则表达式首先过滤掉系统关键字,其次过滤掉不合法变量,再去除掉收尾的都好,最后根据都好分割成数组形式,以此来拼接字符串。再通过上面的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 = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|‘(?:[^‘\\]|\\[\w\W])*‘|\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(/\r/g, ‘\\r‘)
    .replace(/\n/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\n\n‘;
    for (var name in e) {
        message += ‘<‘ + name + ‘>\n‘ + e[name] + ‘\n\n‘;
    }

    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的结构图,刚开始看可能看不明白,等看完源码再看这张图,会感觉清晰很多。技术分享

 

以上是关于artTemplate模板引擎的源码拜读的主要内容,如果未能解决你的问题,请参考以下文章

arttemplate怎么提取公共部分

模板引擎

js模板引擎--artTemplate

性能卓越的js模板引擎--artTemplate

artTemplate高性能数据模板引擎

javascript模板引擎artTemplate.js——template()方法