javascript模板系統 ejs v10


最近一直攻略node.js,發現ejsv9在后端的視圖層有點力不從心。

后端是模板的最大用戶,因此拼字符串必須會死翹翹。通常來說,我們一個action對應一個模板,它應該是只含body部分的HTML,另外,還有一個layout,它是包含head與body的底部。它們兩個加起來,加個模型層的數據生成一個真正的頁面返給前端。但生成這頁面不像普通的挖坑填數字的過程,像ejs、mustache、micro-Templating、doT.js就是如此。不過有的模板可以套嵌大量的邏輯,有的不能,像mustache就號稱Logic-less templates,目的不想讓模板也成為代碼的意大利面條,這是JSP時代的教訓。但javaer在struct建立王者統治的年代,騰出許多精力來研究各種東西,對視圖層提出兩大解決方案,視圖helper與標簽庫。其中標簽庫因為學習成本過高,因此當其他語言發展出的web framework時,視圖helper幾乎是他們唯一的選擇。視圖helper也是返回一段HTML字符串,只不過它與業務層打交道非常頻繁而獨立出來,而它們獨立出來則大大減少視圖亂堆代碼的現象。helper機制就是ejs v10引入的一種重要改進了。

另外,模板里面的內容有兩個地方需要輸出到頁面,一個是定界符左邊與右邊的HTML片斷,另一個是來自JS邏輯的某些數據,如<%= aaa %>這樣的寫法(不同每個模板的語法都不一樣!)PHP著名的smarty模板有個語法糖,可以對這些將要輸出的變量進行進一步加工,即在變量后面加一個|號,后面跟着此過濾器的名字與其傳參,而且可以跟多個|跟其他的過濾器。ejs v10也引進此語法糖。

<%$data.circle.title|escape:"html"%>

在其他重大改進,模板構建算法改進,不再使用轉義。傳統的前端模板都會對源碼中的“'”,“"”,“\\”,“\n”,“\t”,“\f”,“\b”,“\r”進行處理,也就是所謂加雙號過程,雙引號里面的數據進行轉義,防止破壞動態生成的模板函數的結構。ejs v10則將這個HTML數組放到模板函數的外面,從此一勞永逸了!@前綴變量也保證ejs不使用with機制進行對象綁定的。

helper機制的使用,helper函數應該一開始就與模板函數綁在一起的,因此ejs v10的模板函數最初是一個curry函數,它負責傳入HTML數組,filter,與helpers。

//helper機制的使用
 var fn = $.ejs.compile(source, helper);

helper是一個對象,里面盡是函數。

生成的是模板函數,如果你是這樣調用$.ejs(id, data),它就百分之百緩存了這函數。它里面是調用了$.ejs.compile

現在ejs作為我的newland.js項目的一個模塊而存在.

更改日志:


v1

默認界定符為<% %>,當然也可以自定義界定符,只支持當前頁面的script元素做模板

http://www.cnblogs.com/rubylouvre/archive/2010/08/10/1796383.html

v2

改進構建算法提速,比John Resig的 Micro-Templating模板更能應對復雜的模板

http://www.cnblogs.com/rubylouvre/archive/2010/08/22/1805914.html

v3

http://www.cnblogs.com/rubylouvre/archive/2010/08/25/1807789.html

增添了局部模板功能

v4

http://www.cnblogs.com/rubylouvre/archive/2010/08/31/1813122.html

對v3的結構進行優化,支持遠程的獨立文件做模板

v5

http://www.cnblogs.com/rubylouvre/archive/2010/08/31/1813122.html

嘗試新的算法

v6

http://www.cnblogs.com/rubylouvre/archive/2010/10/05/1841933.html

更新默認界定符為<& &>,添加新的操作符<&~,對數據源的第一層屬性名添加@前綴

v7

對參數進行多態化,因ejs天生支持模板的相互調用便去除<&:與<&~操作符

v8
去掉去掉參數多態化,現在只有兩個參數。第一個參數為script標簽的ID,第二個參數對數據對象
去掉@標識符,網友反映這東西很怪
去掉遠程模板支持,因為怎么遠程也一定要同域才行,要不AJAX獲取不到,雞肋。以后模板統一寫到type為"text/html"的scrpt元素中。
優化quote函數。網上有許多JS模板都是直接用正則進行全文轉義,但怎么說也不比上quote函數安全。
使用apply對傳參進行優化。indexOf判定優化。

v9
改回v6的形態,后端數據還是帶@前綴才方便查看與調試
對輸出代碼進行修正,去掉用戶誤寫的逗號或分號

v10
helper機制
filter機制
新的構建算法

我已經整成jQuery插件了,隨便拿去用吧!點我下載

            ;;(function($){
                // by 司徒正美
                //http://www.cnblogs.com/rubylouvre/archive/2012/08/06/2624970.html
                /**
                 * 入口函數
                 * @param {string} id CSS表達式(用於模取元素然后取得里面的innerHTML作為源碼)
                 * @param {Object} data  數據包
                 * @return  {Object} opts 可選參數,可以自由制定你的定界符
                 */
                $.ejs = function( id,data,opts){
                    var el, source
                    if( !$.ejs.cache[ id] ){
                        opts = opts || {}
                        var doc = opts.doc || document;
                        data = data || {};
                        el = $(id, doc)[0];
                        if(! el )
                            throw "can not find the target element";
                        source = el.innerHTML;
                        if(!(/script|textarea/i.test(el.tagName))){
                            source = $.ejs.filters.unescape( source );
                        }
                        var fn = $.ejs.compile( source, opts );
                        $.ejs.cache[ id ] = fn;
                    }
                    return $.ejs.cache[ id ]( data );
                }
                var isNodejs = typeof exports == "object";
                $.ejs.cache = {};
                $.ejs.filters = {
                    //自己可以在這里添加更多過濾器,或者可以到這里面自由提取你喜歡的工具函數
                    //https://github.com/RubyLouvre/newland/blob/master/system/lang.js
                    escape:  function (target) {
                        return target.replace(/&/g,'&amp;')
                        .replace(/</g,'&lt;')
                        .replace(/>/g,'&gt;')
                        .replace(/"/g, """)
                        .replace(/'/g, "'");
                    },
                    unescape: function(target){
                        return  target.replace(/"/g,'"')
                        .replace(/</g,'<')
                        .replace(/>/g,'>')
                        .replace(/&/g, "&"); //處理轉義的中文和實體字符
                        return target.replace(/&#([\d]+);/g, function($0, $1){
                            return String.fromCharCode(parseInt($1, 10));
                        });
                    }
                };
                $.ejs.compile = function( source, opts){
                    opts = opts || {}
                    var open  = opts.open  || isNodejs ? "<%" : "<&";
                    var close = opts.close || isNodejs ? "%>" : "&>";
                    var helperNames = [], helpers = []
                    for(var name in opts){
                        if(opts.hasOwnProperty(name) && typeof opts[name] == "function"){
                            helperNames.push(name)
                            helpers.push( opts[name] )
                        }
                    }
                    var flag = true;//判定是否位於前定界符的左邊
                    var codes = []; //用於放置源碼模板中普通文本片斷
                    var time = new Date * 1;// 時間截,用於構建codes數組的引用變量
                    var prefix = " ;r += txt"+ time +"[" //渲染函數輸出部分的前面
                    var postfix = "];"//渲染函數輸出部分的后面
                    var t = "return function(data){ try{var r = '',line"+time+" = 0;";//渲染函數的最開始部分
                    var rAt = /(^|[^\w\u00c0-\uFFFF_])(@)(?=\w)/g;
                    var rstr = /(['"])(?:\\[\s\S]|[^\ \\r\n])*?\1/g 
                    var rtrim = /(^-|-$)/g;
                    var rmass = /mass/
                    var js = []
                    var pre = 0, cur, code, trim
                    for(var i = 0, n = source.length; i < n; ){
                        cur = source.indexOf( flag ? open : close, i);
                        if( cur < pre){
                            if( flag ){//取得最末尾的HTML片斷
                               t += prefix + codes.length + postfix
                               if(cur == -1 && i == 0){
                                   code = source
                               }else{
                                  code = source.slice( pre+ close.length );
                               }
                                if(trim){
                                    code = $.trim(code)
                                    trim = false;
                                }
                                codes.push( code );
                            }else{
                                $.error("發生錯誤了");
                            }
                            break;
                        }
                        code = source.slice(i, cur );//截取前后定界符之間的片斷
                        pre = cur;
                        if( flag ){//取得HTML片斷
                            t += prefix + codes.length + postfix;
                            if(trim){
                                code = $.trim(code);
                                trim = false;
                            }
                            codes.push( code );
                            i = cur + open.length;
                        }else{//取得javascript羅輯
                            js.push(code)
                            t += ";line"+time+"=" +js.length+";"
                            switch(code.charAt(0)){
                                case "="://直接輸出
                                    code = code.replace(rtrim,function(){
                                        trim = true;
                                        return ""
                                    });
                                    code = code.replace(rAt,"$1data.");
                                    if( code.indexOf("|") > 1 ){//使用過濾器
                                        var arr = []
                                        var str = code.replace(rstr, function(str){
                                            arr.push(str);//先收拾所有字符串字面量
                                            return 'mass'
                                        }).replace(/\|\|/g,"@");//再收拾所有短路或
                                        if(str.indexOf("|") > 1){
                                            var segments = str.split("|")
                                            var filtered = segments.shift().replace(/\@/g,"||").replace(rmass, function(){
                                                return arr.shift();
                                            });
                                            for( var filter;filter = arr.shift();){
                                                segments = filter.split(":");
                                                name = segments[0];
                                                args = "";
                                                if(segments[1]){
                                                    args = ', ' + segments[1].replace(rmass, function(){
                                                        return arr.shift();//還原
                                                    })
                                                }
                                                filtered = "$.ejs.filters."+ name +"(" +filtered + args+")"
                                            }
                                            code = "="+ filtered
                                        }
                                    }
                                    t += " ;r +" +code +";"
                                    break;
                                case "#"://注釋,不輸出
                                    break
                                case "-":
                                default://普通邏輯,不輸出
                                    code = code.replace(rtrim,function(){
                                        trim = true;
                                        return ""
                                    });
                                    t += code.replace(rAt,"$1data.")
                                    break
                            }
                            i = cur + close.length;
                        }
                        flag = !flag;
                    }
                    t += " return r; }catch(e){ $.log(e);\n$.log(js"+time+"[line"+time+"-1]) }}"
                    var body = ["txt"+time,"js"+time, "filters"]
                    var fn = Function.apply(Function, body.concat(helperNames,t) );
                    var args = [codes, js, $.ejs.filters];
                    //console.log(fn+"")
                    return fn.apply(this, args.concat(helpers));
                }
                return $.ejs;
            })(jQuery)

<script type="tmpl" id="table_tmpl">
        <&= title() &>
        <table border=1>
        <&- for(var i=0,tl = @trs.length,tr;i<tl;i++){  -&>
            <&- tr = @trs[i]; -&>
            <tr>
            <td><&= tr.name;; &></td> <td><&= tr.age; &></td> <td><&= tr.sex || "男" &></td>
            </tr>
            <& } &>
        </table>
        <&# 怎么可能不支持圖片 &>
        <img src="<&= @href &>">
</script>
<script>
          
    $(function(){
        var trs = [
            {name:"隱形殺手",age:29,sex:"男"},
            {name:"索拉",age:22,sex:"男"},
            {name:"fesyo",age:23,sex:"女"},
            {name:"戀妖壺",age:18,sex:"男"},
            {name:"竜崎",age:25,sex:"男"},
            {name:"你不懂的",age:30,sex:"女"}
        ]

        var html = $.ejs("#table_tmpl",{
            trs: trs,
            href: "http://images.cnblogs.com/cnblogs_com/rubylouvre/202906/o_type4.jpg"
        },{
            title: function(){
                return "<p>這是使用視圖helper輸出的代碼片斷</p>"
            }
                   
        });
        $("#ejsv10").html(html)

    })
</script>

輸出結果如下:

這是我模板引擎生成的函數,里面看不到要轉義的字符串!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM