模板编译 Vue中对template属性会编译成render方法。在线模板编译器
增添新的包compiler-core/package.json
{
"name": "@vue/compiler-core",
"version": "1.0.0",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
"buildOptions": {
"name": "VueCompilerCore",
"compat": true,
"formats": [
"esm-bundler",
"cjs"
]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我们将开发环境下的打包入口改为 compile-core,这里我们先提供所需要ast的节点类型
export function compile(template){
// 1.将模板转化成ast语法树
const ast = baseParse(template);
// 2.对ast语法树进行转化
transform(ast);
// 3.生成代码
return generate(ast)
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
生成ast语法树
准备语法树相关type
export const enum NodeTypes {
ROOT, // 根节点 Fragment
ELEMENT, // 元素
TEXT, // 文本
COMMENT, // 注释
SIMPLE_EXPRESSION, // 表达式的值
INTERPOLATION, // 插值
ATTRIBUTE, // 属性
DIRECTIVE, // 指令
// containers
COMPOUND_EXPRESSION, // 复合表达式
IF,
IF_BRANCH,
FOR,
TEXT_CALL,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
创建解析上下文
创建解析上下文,并且根据类型做不同的处理解析。 ast转化
function createParserContext(content) {
return {
line: 1,
column: 1,
offset: 0,
source: content, // source会不停的被截取
originalSource: content // 原始内容
}
}
function isEnd(context) {
const source = context.source;
return !source;
}
function parseChildren(context) {
const nodes = [];
while (!isEnd(context)) {
const s = context.source;
let node;
if (s.startsWith('{{')){ // 处理表达式类型
}else if(s[0] === '<'){ // 标签的开头
if(/[a-z]/i.test(s[1])){} // 开始标签
}
if(!node){ // 文本的处理
}
nodes.push(node);
}
return nodes;
}
function baseParse(template){
const context = createParserContext(template);
return parseChildren(context);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
处理文本节点
采用假设法获取文本结束位置
function parseText(context) { // 123123{{name}}</div>
const endTokens = ['<', '{{'];
let endIndex = context.source.length; // 文本的总长度
// 假设遇到 < 就是文本的结尾 。 在假设遇到{{ 是文本结尾。 最后找离的近的
// 假设法
for (let i = 0; i < endTokens.length; i++) {
const index = context.source.indexOf(endTokens[i], 1);
if (index !== -1 && endIndex > index) {
endIndex = index;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
处理文本内容,删除匹配到的结果,计算最新上下文位置信息
function parseText(context) {
// ...
let start = getCursor(context); // 1.获取文本开始位置
const content = parseTextData(context, endIndex); // 2.处理文本数据
return {
type: NodeTypes.TEXT,
content,
loc: getSelection(context, start) // 3.获取全部信息
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
function getCursor(context) { // 获取当前位置
let { line, column, offset } = context;
return { line, column, offset }
}
function parseTextData(context, endIndex) {
const rawText = context.source.slice(0, endIndex);
advanceBy(context, endIndex); // 截取内容
return rawText
}
function advanceBy(context, endIndex) {
let s = context.source;
advancePositionWithMutation(context, s, endIndex) // 更改位置信息
context.source = s.slice(endIndex);
}
function advancePositionWithMutation(context, s, endIndex) { // 更新最新上下文信息
let linesCount = 0; // 计算行数
let linePos = -1; // 计算其实行开始位置
for (let i = 0; i < endIndex; i++) {
if (s.charCodeAt(i) === 10) { // 遇到\n就增加一行
linesCount++;
linePos = i; // 记录换行后的字节位置
}
}
context.offset += endIndex; // 累加偏移量
context.line += linesCount; // 累加行数
// 计算列数,如果无换行,则直接在原列基础 + 文本末尾位置,否则 总位置减去换行后的字节位置
context.column = linePos == -1 ? context.column + endIndex : endIndex - linePos
}
function getSelection(context,start){
const end = getCursor(context);
return {
start,
end,
source:context.originalSource.slice(start.offset,end.offset)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
转化成最终ast
节点结果,标记ast
节点类型
处理表达式节点
获取表达式中的变量,计算表达式的位置信息
function parseInterpolation(context) {
const start = getCursor(context); // 获取表达式的开头位置
const closeIndex = context.source.indexOf('}}', '{{'); // 找到结束位置
advanceBy(context, 2); // 去掉 {{
const innerStart = getCursor(context); // 计算里面开始和结束
const innerEnd = getCursor(context);
const rawContentLength = closeIndex - 2; // 拿到内容
const preTrimContent = parseTextData(context, rawContentLength);
const content = preTrimContent.trim();
const startOffest = preTrimContent.indexOf(content);
if (startOffest > 0) { // 有空格
advancePositionWithMutation(innerStart, preTrimContent, startOffest); // 计算表达式开始位置
}
const endOffset = content.length + startOffest;
advancePositionWithMutation(innerEnd, preTrimContent, endOffset)
advanceBy(context, 2);
return {
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
content,
loc: getSelection(context, innerStart, innerEnd) // 需要修改getSelection方法
},
loc: getSelection(context, start)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
处理元素节点
处理标签
获取标签名称,更新标签位置信息
function advanceSpaces(context){
const match = /^[ \t\r\n]+/.exec(context.source);
if(match){
advanceBy(context,match[0].length);
}
}
function parseTag(context){
const start = getCursor(context); // 获取开始位置
const match = /^<\/?([a-z][^ \t\r\n/>]*)/.exec(context.source); // 匹配标签名
const tag = match[1];
advanceBy(context,match[0].length); // 删除标签
advanceSpaces(context); // 删除空格
const isSelfClosing = context.source.startsWith('/>'); // 是否是自闭合
advanceBy(context,isSelfClosing?2:1); // 删除闭合 /> >
return {
type:NodeTypes.ELEMENT,
tag,
isSelfClosing,
loc:getSelection(context,start)
}
}
function parseElement(context) {
// 1.解析标签名
let ele = parseTag(context);
if(context.source.startsWith('</')){
parseTag(context); // 解析标签,标签没有儿子,则直接更新标签信息的结束位置
}
ele.loc = getSelection(context,ele.loc.start); // 更新最终位置
return ele;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
处理子节点
递归处理子节点元素
function isEnd(context) {
const source = context.source;
if(context.source.startsWith('</')){ // 如果遇到结束标签说明没有子节点
return true;
}
return !source;
}
function parseElement(context) {
let ele = parseTag(context);
const children = parseChildren(context); // 因为结尾标签, 会再次触发parseElement,这里如果是结尾需要停止
if(context.source.startsWith('</')){
parseTag(context);
}
ele.loc = getSelection(context,ele.loc.start); // 更新最终位置
(ele as any).children = children; // 添加children
return ele;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
处理属性
在处理标签后处理属性
function parseTag(context){
const start = getCursor(context);
const match = /^<\/?([a-z][^ \t\r\n/>]*)/.exec(context.source);
const tag = match[1];
advanceBy(context,match[0].length);
advanceBySpaces(context);
let props = parseAttributes(context); // 处理属性
// ......
return {
type:NodeTypes.ELEMENT,
tag,
isSelfClosing,
loc:getSelection(context,start),
props
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function parseAttributes(context) {
const props: any = [];
while (context.source.length > 0 && !context.source.startsWith('>')) {
const attr = parseAttribute(context)
props.push(attr);
advanceSpaces(context); // 解析一个去空格一个
}
return props
}
function parseAttribute(context) {
const start = getCursor(context);
const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!
const name = match[0]; // 捕获到属性名
advanceBy(context, name.length); // 删除属性名
let value
if (/^[\t\r\n\f ]*=/.test(context.source)) { // 删除空格 等号
advanceSpaces(context);
advanceBy(context, 1);
advanceSpaces(context);
value = parseAttributeValue(context); // 解析属性值
}
const loc = getSelection(context, start)
return {
type: NodeTypes.ATTRIBUTE,
name,
value: {
type: NodeTypes.TEXT,
content: value.content,
loc: value.loc
},
loc
}
}
function parseAttributeValue(context) {
const start = getCursor(context);
const quote = context.source[0];
let content
const isQuoteed = quote === '"' || quote === "'";
if (isQuoteed) {
advanceBy(context, 1);
const endIndex = context.source.indexOf(quote);
content = parseTextData(context, endIndex); // 解析引号中间的值
advanceBy(context, 1);
}
return { content, loc: getSelection(context, start) }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
处理空节点
function parseChildren(context) {
const nodes: any = [];
while (!isEnd(context)) {
//....
}
for(let i = 0 ;i < nodes.length; i++){
const node = nodes[i];
if(node.type == NodeTypes.TEXT){ // 如果是文本 删除空白文本,其他的空格变为一个
if(!/[^\t\r\n\f ]/.test(node.content)){
nodes[i] = null
}else{
node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
}
}
}
return nodes.filter(Boolean)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
创建根节点
将解析出的节点,再次进行包裹,这样可以支持模板下多个根节点的情况, 也是我们常说的 Fragment
export function createRoot(children,loc){
return {
type:NodeTypes.ROOT,
children,
loc
}
}
function baseParse(template) {
// 标识节点的信息 行 列 偏移量
const context = createParserContext(template);
const start = getCursor(context);
return createRoot(
parseChildren(context),
getSelection(context,start)
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16