vue匹配模板简单实现

之前在学习新框架的时候,只是简单的跟着教程一步一步的敲demo,从来没有想过这个框架是如何实现的,简单的功能我能不能实现。最近打算开始学习Vue,觉得还是尝试一下能不能自己简单的实现其中的某些功能吧。

感觉模板引擎后端同学用的多一些,像jade、FreeMarker等,这个通常是在客户端发出HTTP请求之后,应用层的控制器(Controller)接收,通过应用层的服务器(service)访问数据库,然后封装到模型层(Model),再跳转到视图层(View),通过模板引擎生成HTML代码,最后返回给客户端浏览器。

但是Vuejs也是有模板的,他的模板引擎是在客户端浏览器的内存中处理生成的。所以我们用模板的时候,显示出来的是要渲染的数据。

Vue官网的第一个demo

<div id="app">
    {{ message }}
</div>
var app = new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue!'
    }
});

看到这个部分,感觉应该可以通过正则来简单的实现一下:
html部分不变。

正则部分:

首先想到的是匹配两边的大括号,/\{\{\}\}/,注意转译;
然后我们需要匹配大括号中间的内容,[\w]+;
这里需要注意一个问题,在匹配的内容前后可能会有空格,所以这里内容部分我们需要修改成\s*[\w]+\s*;
但是如果需要替换的话,使用正则的分组用法$1会比较轻松的解决这个问题;
所以正则的最终版就变成了/(\{\{\s*([\w]+)\s*\}\})/g;

js部分:
function vue(options) {
    var context = document.querySelector(options.el);
    var html = context.innerHTML;
    var regex = /(\{\{\s*([\w]+)\s*\}\})/g;
    while(regex.test(html)) {
        html = html.replace(RegExp.$1, options.data[RegExp.$2]);
    }
    context.innerHTML = html;
}

$1是最先匹配到的{% raw %}{{}}{% endraw %}
包括其内的部分,$2是匹配的要替换的对象。
使用方法跟vue的使用方法一样。
这样第一个demo我们就简单的实现了。当然相比vue有非常多的缺陷,比如不可以通过app.message来修改数据…

继续阅读vue匹配模板简单实现

前端构建工具的简易实现

虽然说之前用过gulp、webpack这种前端构建工具,但是自己也只是单纯的使用,从来没想过这些东西到底是怎样实现的。最近看了一节公开课,觉得很受启发,现在就说一下我的这个前端构建工具是如何实现的。

功能

开发环境

zst dev 实现保存代码自动刷新的功能。

生产环境

zst dist 这个实现的功能较多:
1. 自动生成dist目录
2. 合并压缩js、css添加时间戳输出到dist目录
3. 将favicon.ico文件复制到dist目录
4. 处理index.html文件,删除注释
5. 如果开发的是移动端页面,还会在index.html中插入移动端meta及处理rem的代码

用到的模块:

express、watch、command、fs、path、uglifyjs、uglifycss、cheerio、open、package、http、socket.io

开发

config

我们需要一个config对象来作为项目的配置

const config = {
    server: {
        ip: "http://localhost",
        port: 3000
    },
    input: "./src",
    output: "./dist",
    info: "INFO  ",
    isPhone: true, //是否是手机
};

server字段设置的是服务的ip跟端口,input字段是输入路径,output字段是输出路径,info字段是打印log的日志头,isPhone字段是是否是移动端。

开发环境

  1. 首先,我们需要创建一个http服务在3000端口,这里我们使用express这个框架来实现。
var app = require('express');
var server = require('http').createServer(app);
server.listen(config.server.port, function (req, res) {
    log('server start at '+config.server.ip+":"+config.server.port);
});
app.get('/', function (req, res) {
    res.send(getHtml(config.isPhone,config.input));
});

function getHtml(isPhone,path) {
    var path = path || './src';
    var devHtml = fs.readFileSync(path +'/index.html', 'utf-8') + fs.readFileSync('./socket.xml');
    if(isPhone) return fs.readFileSync('./isphone.xml') + devHtml;
    else return devHtml;
}

在这段代码中,server.listen是在config.server.port端口开启一个服务器,cb会在服务开启后执行。
app.get()是express的路由,当客户端访问到/时,返回给客户端什么内容。
getHtml方法最终返回的是加上处理自动刷新代码的index.html。
如果要开启服务器后自动打开客户端,可以使用open插件:

var open = require('open'); //自动打开浏览器
server.listen(config.server.port, function (req, res) {
    log('server start at '+config.server.ip+":"+config.server.port);
    open(config.server.ip+":"+config.server.port);
});
  1. 使用socket.io及watch
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
    watch.watchTree(config.input, function (f, curr, prev) {
        if (typeof f == "object" && prev === null && curr === null) {
            // Finished walking the tree
        } else { // f was changed
            socket.emit('file-change');
        }
    });
    log('client connected');
});

socket.io是websocket长链接通信的封装,之前在egret游戏中使用了websocket通信,但是没有使用socket.io。
这个就是当socket连接后,执行的操作,也就是使用watch插件进行文件的监控。使用方式都有注释,也可以从github上进行搜索。最后一个else中,socket.emit是自定义的事件名,文件改变。

继续阅读前端构建工具的简易实现

shell基本命令

因为公司里有个项目改完ui层后,需要运行很多命令才能讲ui层打包并重启项目,那些命令一个一个的去复制感觉有些麻烦,所以想弄个shell脚本来解决这个问题。

1. Hello World!

脚本
#!/bin/bash  // #!是一个约定,告诉系统这个脚本需要何种解释器来执行  
echo "Hello World!" //echo命令用于向窗口输出文本。
执行

将上面的代码保存为test.sh,并cd到相应目录:
chmod +x ./test.sh,使脚本具有执行权限;
./test.sh,执行脚本。

注意

必须写成./test.sh,否则系统会从PATH中寻找;如果使用/bin/sh作为解释器,第一行可以省略。

2. read命令

#!/bin/bash

echo "Hello, what is your name?"
read PERSON
echo "Hello, $PERSON!" #此处必须是双引号,单引号不会使用变量的值

通过read命令从stdin获取输入并赋值给PERSON变量,最后在stdout上输出。

3. 定义变量

#!/bin/bash

myUrl="http://www.zhangshuang.top"
myName="张爽"
myAge = "26" #识别不出来,不能有空格

echo "My name is ${myName}"
echo "My blog site is $myUrl"
echo "I'm ${myAge} years old" #这句话会报错

变量名在使用的时候,前面必须有$符号,可选添加{% raw %}{}{% endraw %}表示变量边界。从新定义变量跟第一次定义变量写法一样。

4. readonly 只读变量

#!/bin/bash
myUrl="http://www.zhangshuang.top"
readonly myUrl
myUrl="http://zhangshuang.top" #此句运行时会报错,提示该变量为只读变量

5. unset 删除变量

#!/bin/bash
myUrl="http://www.zhangshuang.top"
unset myUrl
echo "${myUrl}" #此句运行时会报错,提示找不到变量

myUrl="http://www.zhangshuang.top"
readonly myUrl
unset myUrl #报错,只读变量无法删除
echo "${myUrl}"

6. 获取字符串长度

#!/bin/bash
url="http://www.zhangshuang.top"
echo "url length is $#url" #${#url}获取字符串length

7. 提取字符串

#!/bin/bash
url="http://www.zhangshuang.top"
echo "${url:1:4}" #从第二个字符到第五个字符(索引从0开始)

8. if…else

if [ $PERSON == "shuang" ] #注意必须有空格
then
    echo "My name is ${myName}" #缩进可以没有,有的话可读性高一些
    echo "My blog site is ${myUrl}"
    echo "I'm ${myAge} years old"
fi #表示结束

9. 完成一个命令后再执行另一个命令

#!/bin/bash
starttime=$(date +%s) #获取开始执行脚本的时间毫秒数

cd /usr/Hyg #进入项目路径
output=`docker rm -f hygui` #通过output使得该命令执行完后再执行下个命令
output=`docker rmi hyg-ui`
output=`mvn clean package -pl UI docker:build`
output=`docker run -d -h="hygui" -it -p 8088:80 --link`

10. 计算时间差

#!/bin/bash
starttime=$(date +%s) #获取开始执行脚本的时间毫秒数

# date +%Y-%m-%d获取当前日期
# 脚本内容
# 脚本内容

endtime=$(date +%s) #获取开始执行脚本的时间毫秒数
interval=`expr ${endtime} - ${starttime}` #计算项目运行时间

echo "INFO  generated in ${interval} s" #输出时间差

doTjs详细介绍

doT.js是一个小巧快速的JavaScript模板引擎,他可以帮助我们在html端更好的使用json数据,且无需依赖其他插件就可工作。整个插件只有120行,.min.js文件甚至只有4kb。
官网:http://olado.github.io/doT/index.html

安装

npm install dot

1. 用于nodejs

require('dot');

2. 浏览器

<script type="text/javascript" src="./doT.js"></script>

使用

html:
<div id="int"></div>

1. 赋值

格式:
{% raw %}
{{= }}
{% endraw %}

模板:

<script id="intTmpl" type="text/template">
    <p>My name is {{=it.name}}, I'm {{=it.age}}</p>
</script>

调用方式:

var data = {name: 'John', age: 26};
var intTmpl = doT.template(document.getElementById('intTmpl').innerText);
document.getElementById('int').innerHTML = intTmpl(data);

从上面的代码中我们可以看到,有一个it,这个是代指引用的数据,也就是代码中的datait.name就是data.name,所以这个在使用方法上实际跟json对象是一样一样的。
在调用方式上,都是3个步骤:
1. 先声明数据源,也就是代码中的data,这个一般情况下是后端同学传回来的。
2. 找到要将数据填进去的模板。
3. 找到你要渲染的位置。这个位置就是上面html中的int

2. 条件

格式:

{% raw %}
{{? }}
{% endraw %} //if
{% raw %}
{{?? }}
{% endraw %} //else if
{% raw %}
{{??}}
{% endraw %} //else

模板:

<script id="intTmpl" type="text/template">
    {{? it.name}}
    <p>Hello, my name is {{=it.name}}</p>
    {{?? it.age}}
    <p>I'm {{=it.age}}</p>
    {{??}}
    <p>Nobody here!</p>
</script>

调用方式:

var data = {name: 'John', age: '26'};
var intTmpl = doT.template(document.getElementById('intTmpl').innerText);
document.getElementById('int').innerHTML = intTmpl(data);

调用方式都是一样的。

3. 转译html

格式:
{% raw %}
{{! }}
{% endraw %}
模板:

<script id="intTmpl" type="text/template">
    {{!it.uri}} {{!it.html}}
</script>```
调用方式:  

var data = {uri:”http://www.zhangshuang.top”, html:”html元素“};
剩下的跟上面一样。


转译跟赋值的区别: 转译是让html标签不使用html引擎渲染,也就是不会解析html构建DOM树,也不渲染树结构、布局、绘制样式等等,而是直接变成字符串渲染出来。 赋值的话,如果数据中有html标签,就会解析html构建DOM树,渲染树结构,渲染树布局,绘制渲染树。 ##### 4. 数组迭代 格式: {% raw %} {{~ }} {% endraw %} 模板:

{{~it.array:value:index}}

{{=index+1}}:{{=value}}

{{~}}


调用方式:

var data = {array: [“a1”, “a2”, “a3”]};
剩下两个步骤跟上面一样。
“`

其中it.array是找到那个数组value是数组中的每一个值,index是数组的下标,换成a或b或其他都行。

上面这些事比较常用的,甚至,只要会使用json都能很好的使用doT.js,也就是每一个都通过赋值的方式去找。还有一些其他的,如果有想了解的也可以去官网了解一下。