如何实现 Vue 转小程序 (2)
parse
接着,我们来实现传入的 startCb、endCb 和 CharsCb 方法:
parser.parse = source => {
const res = [];
const stack = [];
parseHtml(source, start, end, chars);
return res;
function start(tag, attrs, selfClose, start, end) {}
function end(tag, start, end) {}
function chars(text, start, end) {}
};
res 是返回的结果,stack 是用来暂存的栈,start、end 和 chars 是我们要实现的方法,我们先来看看 start 方法:
function start(tag, attrs, selfClose, start, end) {
const element = {
tag,
attrs,
selfClose,
children: [],
start,
content: []
};
if (stack.length > 0) {
stack[stack.length - 1].children.push(element);
if (!selfClose) {
stack.push(element);
} else {
element.end = end - 1;
}
} else {
res.push(element);
stack.push(element);
}
}
start 方法中先声明一个 element 对象,其中包括标签名 tag,属性 attrs,是否为自闭合标签 selfClose,子元素 children,开始 index start,文本内容 content
再对 stack 的长度进行判断,如果大于 0,则说明处于其他标签之中,将 element 传入 stack 最后一个元素的 children 中;然后判断 selfClose,若不是是自闭合标签,则放入栈中;
如果 stack 长度为 0,则是根标签,将 element 直接存入 res 中,并放入栈中
再看看 end 方法,stack 出栈:
function end(tag, start, end) {
const element = stack.pop();
element.end = end - 1;
}
然后是 chars 方法,将文本内容传入栈顶元素的 content:
function chars(text, start, end) {
if (stack.length > 0) {
const top = stack[stack.length - 1];
top.content.push({
text,
start,
end
});
}
}
至此,传入 source,parse 方法可以将 source 转为一个数组,我们来实验一下:
const source = `<template lang="vue" vue>
<div>
test
<img />
<text>123</text>
</div>
</template>`;
const res = parser.parse(source);
console.log(util.inspect(res, false, null));
最后输出:
serialize
vue 单文件可以转化为 js 对象,那 js 对象当然可以转化为 vue 单文件内容,接下来我们就来实现 serialize 方法:
parser.serialize = node => {
let source = "";
node.forEach(item => {
source += serializeNode(item) + "\n";
});
return source;
function serializeNode(node) {}
};
serialize 接受一个数组,遍历 node,将子 node 传入 serializeNode 方法获取序列化结果,serializeNode 方法如下:
function serializeNode(node) {
const { tag, attrs, selfClose, children, content } = node;
let attrString = "";
attrs.forEach(attr => {
const { name, value } = attr;
if (value === true) {
attrString += ` ${name}`;
} else {
attrString += ` ${name}=${value}`;
}
});
if (selfClose) {
return `<${tag}${attrString} />`;
}
const childrenLength = children.length;
const contentLength = content.length;
let serializedChildNodes = "";
let childrenIndex = 0;
let contentIndex = 0;
while (childrenIndex < childrenLength || contentIndex < contentLength) {
if (childrenIndex >= childrenLength) {
serializedChildNodes += content[contentIndex++].text;
continue;
}
if (contentIndex >= contentLength) {
serializedChildNodes += serializeNode(children[childrenIndex++]);
continue;
}
serializedChildNodes +=
content[contentIndex].start > children[childrenIndex].start
? serializeNode(children[childrenIndex++])
: content[contentIndex++].text;
}
return `<${tag}${attrString} >${serializedChildNodes}</${tag}>`;
}
先将 node 中的 attr 数组转化为字符串的形式,再判断是否为自闭合标签,若为自闭合标签,直接返回<${tag}${attrString} />
再对content
和children
进行遍历,遍历的规则是根据起始 index 数 start 依次遍历,若content[contentIndex].start<children[childrenIndex].start
,则拿到content[contentIndex]
的text
,将其加到serializedChildNodes
中;反之调用serializeNode()
方法,并传入children[childrenIndex++]
,将返回值加到serializedChildNodes
中。
遍历完成后返回<${tag}${attrString} >${serializedChildNodes}</${tag}>
到了这里,serialize 方法也已完成,我们来试一下:
const source = `<template lang="vue" test="11" xx yy>
<div>
test
<img />
<text>123</text>
</div>
</template>
<script>
export default {
name: "Test",
components: {},
data: function() {
return {};
}
};
</script>
<style scoped>
</style>`;
const res = parser.parse(source);
console.log(parser.serialize(res));
可以看到,最后输出的字符与 source 基本一致(有些空白字符没有完美处理,所以会有点出入,但不影响代码结构)