编写一个简单的js模板替换工具 rtt----replace templete tool

gitbub

npm install rtt --save-dev

  

  最近一段时间在修改自己的个人在线简历.  这个在线简历用到了css3来制作3D的旋转效果, 因此会有兼容性问题, 针对于不支持css3的3D透视的浏览器, 比如 IE,  360等等, 我使用的是另一套css文件兼容.  针对于移动端浏览器, 尽管基本都是webkit内核, 但经测试发现3D效果并不流畅, 因此移动端是识别userAgent切换到另一套非3D页面.  因为没用任何数据库, 那么问题就来了, 移动端和pc端两套页面是共用的一套数据, 我想到的方法有两个: 一是页面加载之后用ajax请求同一个数据文件, 然后js在页面显示的时候填充数据, 但这个方法要增加http请求, 万一碰到需要请求超多数据文件的时候呢? 加载速度自然要受影响.  方法二在html中直接写,这样不会增加多余的请求, 但是万一里面的数据要修改一下呢, 多个页面你去慢慢找吧......   因此就想实现自己的一套模板替换工具, 像php那样插入, 然后就能直接打印出来. 正好最近在学nodejs, 它可以操作文件, 对于我们jser来说犹如神器! 于是我就想模拟php插入标签方式实现一个简单的模板替换工具rtt ---- replace templete tool, 通过nodejs来操作html文件, 然后替换输出到目标文件

编写一个简单的js模板替换工具 rtt----replace templete tool_第1张图片

     在html插入<%%>类型的标签, 然后里面可以使用js代码:

<div class="face info">
<%
	for(var i=0, m=data.info.length; i<m; i++){
%>
		<div class="info-kind">
			<h2><%=data.info[i].title%></h2>
			<p><%=data.info[i].content%></p>
		</div>
<%
	}
%>
</div>

  替换之后, 生成内容如下:

<div class="face info">
    <div class="info-kind">
        <h2>工作经历</h2>
        <p>###########</p>
    </div>
    <div class="info-kind">
        <h2>兴趣爱好</h2>
        <p>###########</p>
    </div>
    <div class="info-kind">
        <h2>英语水平</h2>
        <p>###########</p>
    </div>
    ......
</div>

  

实现模板替换工具

  如何实现一个这种工具? 其实原理非常简单, 就是用运行在node中的js读取文件, 会用到 fs.readFileSync 方法, 然后将得到的字符串用正则进行匹配, 通过<%%>作为定界符, js就可以区分开哪些是直接输出, 哪些是需要经过运算, 最后用fs.writeFileSync方法输出. 因为之前自己实现过一个复杂dom选择器, 所以一下就想到使用正则断开特殊字符, 然后放到一个数组中, 进行遍历.

  新建一个rtt.js的文件:

var fs = require('fs');   //获取fs模块

var srcHtml = fs.readFileSync('a.src.html');    //读取a.src.html文件数据  

srcHtml = srcHtml.toString('utf-8');    //将读取到的二进制数据转成字符串

srcHtml = srcHtml.replace(/[\r\n\t]+/g, '');    //去掉换行和tab

//本可以和上面合并, 方便观看分开写, 去掉html和js注释
srcHtml = srcHtml.replace(/<!--.*?-->|\/\*.*?\*\//g, '');

//将srcHtml用<%%>断开
//如果是  abc<% alert(1); %>def 会输出['abc', '<% alert(1); %>', 'def']
var arr = srcHtml.match(/<%={0,2}.*?%>|.*?(?=<%)|.*?(?=$)/g)

  因此得到的arr是类似于 ['<div>', '<% data.name %>', '</div>', '<% data.age %>', '<ul>' ........ ]

最后需要通过eval才能读取<%%>中的js语句, 所以需要设置一个用于eval的s变量, s变量中有res变量最终会被eval出来,在遍历数组的过程中不断迭代, 用于输出

var s = "res='';";

for(var i=0, m=arr.length; i<m; i++){
    /*遍历数组的时候, 进行判断, 如果是<%开头的, 则是js语句, 如果不是则直接输出*/

    if(arr[i].indexOf('<%') === 0 ){
        if(arr[i].charAt(2) === '=' && arr[i].charAt(3) !== '=' ){
            //如果是js变量的时候
            s += 'res+=' + arr[i] + ';' ;
        }
         else {
             //js语句的时候
             s += arr[i];
         }
    }
    else {
        s += 'res+="' + arr[i] + '";' ;
    }
}

eval(s);    //生成res变量, 并执行

fs.writeFileSync(a.html, res);    //将res变量输出到目标文件

  

    现在, 一个简单的模板替换工具就完成了, 每次的arr[i]还需要检验, 转义这里就不赘述了, 声明一个函数调用即可.

添加include函数用来引入其他文件内容

    上面的模板替换只允许替换变量, 通过数组生成多个指定标签, 但我们经常需要引入一些外部的模块文件, php中的require和include非常好用, 但现在是nodejs.

比如该说a.src.html中引入了m.tpl.html, m.tpl.html又引入了x.tpl.js和y.tpl.html, y.tpl.html中又引入了z.tpl.json.  

原理也没那么难,首先将上面的代码封装成一个函数.

/*
src --- 源文件路径
dest --- 生成文件路径
dataPath --- json数据路径
dataName --- src文件中的对象名字
delimiter --- 定界符
*/

function rtt(src, dest, dataPath, dataName, delimiter){
    //设置对象名字默认为dataPath的文件名
    dataName = dataName || dataPath.split('/')[dataPath.split('/').length-1].replace(/\.[a-z0-9]+$/gi, '');

    //设置定界符默认是<%%>, 然后在中间分隔开保存成['<%', '%>']
    delimiter = delimiter || '<%%>';
    delimiter = (function(){
        var n = Math.floor(delimiter.length/2);
        return [delimiter.substr(0, n), delimiter.substr(-n)];
    })();

    //调用replace函数, 最终输出得到替换之后的字符串, 生成到dest文件
    //这里大部分替换逻辑已经封装到了replace中, replace只读取源文件生成目标最终字符串
    //因为replace中会使用的rtt中的各种变量, 因此使用闭包形式书写
    fs.writeFileSync(dest, replace(src));

    console.log('compile finished!');    //打印'compile finished'

    function replace(src){
//传入一个src路径, 返回替换之后的字符串 ...... } }

  

replace大部分和上上面试一样的, 只是封装成了一个函数, 然后进行了include扩展:

function replace(src){
    eval("var "+dataName+" =" + data);

    var s = "res='';",
          fs = require("fs"),
          location = getLocation(src);

    //读取源文件html, 并得到字符串, 然后将字符串中的\r\n\t
    var srcHtml = fs.readFileSync(src).toString('utf-8').replace(/[\r\n\t]+/g, '');

    //去掉html注释, js注释/**/类型
    srcHtml = srcHtml.replace(/<!--.*?-->|\/\*.*?\*\//g, '');

    //分割html文件中的字符串
    var reg = new RegExp(delimiter[0] + "={0,2}.*?"+delimiter[1]+"|.*?(?="+delimiter[0]+")|.*?(?=$)", "g");
    var arr = srcHtml.match(reg);


    for(var i=0, m=arr.length; i<m; i++){
        if(arr[i].indexOf('<%') === 0){
             if(arr[i].charAt(2)==='=' && arr[i].charAt(3) !== '=' ){
                 //js变量的时候
                 s += 'res+=' + trim(arr[i]) + ';';
             }
             else if(arr[i].charAt(2)==='=' && arr[i].charAt(3) === '='){
                 //js变量的时候, 如果是两个等号, 则将其中<替换成&lt; 将> 替换成&gt;
		 s += 'res+=' + esHtml(trim(arr[i])) + ';';
             }
             else if(/^<%\s*include\(.*\)\s*;?\s*%>$/.test(arr[i])){
                 //如果识别到是include('abc.html')类型格式, 则递归调用replace函数, 传入abc.html
                 var newPath = genPath(src, arr[i].replace(/.+['"](.+)['"].+/g, function(a, b){ return b; }));
                 s += 'res+="' + es(replace( newPath )) + '";';
             }
             else{
                   //js语句的时候
                   s += trim(arr[i]);
             }
         }
         else{
             s += 'res+="' + es(arr[i]) + '";';
         }

     }
     eval(s);

     //返回eval得到的变量res
     return res;
}

  上面的replace函数中用到了递归来实现多层引用, 但是有个问题, 如果 a.src.html 中引用了 tpl/b.html,  tpl/b.html 又引用了 ./css/c.css, 那么路径就会出现问题, 因为被引用的文件中, 引用其他文件的路径是相对于他自己, 而node读取文件的位置是src文件所在的位置, 如图所示:

编写一个简单的js模板替换工具 rtt----replace templete tool_第2张图片

当node执行到include('css/c.html')的时候, 会根据src文件的位置, 读取/css/c.css文件, 因此会出错, 所以需要使用函数来生成新的相对于src的路径, 才能得到/tpl/css/c.css文件

//传入一个文件路径,返回其所在目录
function getLocation(src){
	if(src.charAt(src.length-1) === '/') return src;
	else if(src.indexOf('/') === 0) return './';
	else if(src.indexOf('/') < 0) return './';
	else{
		var temp = src.split('/');
		temp.pop();
		return temp.join('/')+'/';
	}
}

然后还需要生成新路径的函数:

//传入当前文件路径, 和include的文件路径, 生成相对于index的相对路径
function genPath(cur, dest){
	if(dest.charAt(0) !== '.' && dest.charAt(0) !== '/'){
		return getLocation(cur) + dest.replace(/^\//, '');
	}
	else if(dest.indexOf('./') === 0){
		return getLocation(cur) + dest.replace(/^\.\//, '');
	}
	else{
		//如果目标路径是 ../../../a/b/c.html类型
		var cur = getLocation(cur).match(/[^\/]+\//g);
		var m = cur.length;
		var n = dest.match(/\.\.\//g).length;
		if(m>n){
			return cur.slice(0, m-n).join('') + dest.replace(/\.\.\//g, '');
		}
		else if(m === n) return './' + dest.replace(/\.\.\//g, '');
		else if(m < n){
			for(var res='', i=0; i<n-m; i++) res+='../';
			return res + dest.replace(/\.\.\//g, '');
		}
	}
}

  

至此, 一个可以引用其他文件的模板已经完成了, 可以通过npm install rtt --save-dev 进行安装, 然后新建一个replace.js文件, 输入:

var rtt = require('rtt');

rtt('a.src.html' , 'a.html', 'data.json');

就可以讲a.src.html源文件中<%%>包含的使用的data命名空间输出到a.html, 允许多层引入文件, 注意: <%include('');%>

由于本人学习nodejs时间不长, 有任何问题请各位指出, 多谢啦

 

 

 

     

 

你可能感兴趣的:(replace)