Skip to content

Commit acedd1a

Browse files
committed
refactor: demo
1 parent 493a165 commit acedd1a

File tree

4 files changed

+111
-287
lines changed

4 files changed

+111
-287
lines changed

demo/20-parse-template.html

Lines changed: 8 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -1,270 +1,15 @@
11
<body></body>
22

3-
<script>
4-
const NodeTypes = {
5-
// AST
6-
ROOT: 'ROOT',
7-
ELEMENT: 'ELEMENT',
8-
TEXT: 'TEXT',
9-
SIMPLE_EXPRESSION: 'SIMPLE_EXPRESSION',
10-
INTERPOLATION: 'INTERPOLATION',
11-
ATTRIBUTE: 'ATTRIBUTE',
12-
DIRECTIVE: 'DIRECTIVE',
13-
}
14-
15-
/**
16-
* 模板解析
17-
* @param {String} str 模板字符串
18-
*/
19-
function parse(str) {
20-
const context = {
21-
// 存储模板字符串
22-
source: str,
23-
// 前进 num 个字符
24-
advanceBy(num) {
25-
context.source = context.source.slice(num)
26-
},
27-
advanceSpaces() {
28-
// 匹配空格
29-
const match = /^[\t\r\n\f ]+/.exec(context.source)
30-
if (match) {
31-
context.advanceBy(match[0].length)
32-
}
33-
}
34-
}
35-
const nodes = parseChildren(context, [])
36-
// 根节点
37-
const root = {
38-
type: NodeTypes.ROOT,
39-
children: nodes
40-
}
41-
return root
42-
}
43-
44-
/**
45-
* 解析子节点
46-
* @param {Object} context 上下文
47-
* @param {Array} ancestors 祖先节点
48-
*/
49-
function parseChildren(context, ancestors = []) {
50-
const nodes = []
51-
52-
while (!isEnd(context, ancestors)) {
53-
let node
54-
const s = context.source
55-
56-
if (s.startsWith('{{')) {
57-
// 解析插值表达式
58-
node = parseInterpolation(context)
59-
} else if (s[0] === '<') {
60-
if (s[1] === '/') {
61-
// 结束标签
62-
} else if (/[a-z]/i.test(s[1])) {
63-
// 解析开始标签
64-
node = parseElement(context, ancestors)
65-
}
66-
}
67-
if (!node) {
68-
node = parseText(context)
69-
}
70-
nodes.push(node)
71-
}
72-
73-
return nodes
74-
}
75-
76-
/**
77-
* 解析插值表达式
78-
* @param {*} context 上下文
79-
* @returns
80-
*/
81-
function parseInterpolation(context) {
82-
const { advanceBy } = context
83-
// 移除 {{
84-
advanceBy(2)
85-
const closeIndex = context.source.indexOf('}}')
86-
const rawContent = context.source.slice(0, closeIndex)
87-
// 去掉前后空格
88-
const content = rawContent.trim()
89-
advanceBy(rawContent.length)
90-
// 移除 }}
91-
advanceBy(2)
92-
93-
return {
94-
type: NodeTypes.INTERPOLATION,
95-
content: {
96-
type: NodeTypes.SIMPLE_EXPRESSION,
97-
content
98-
}
99-
}
100-
}
101-
102-
/**
103-
* 解析元素
104-
* @param {*} context
105-
* @param {*} ancestors
106-
* @returns
107-
*/
108-
function parseElement(context, ancestors) {
109-
// 解析开始标签
110-
// <div></div>
111-
const element = parseTag(context)
112-
113-
ancestors.push(element)
114-
element.children = parseChildren(context, ancestors)
115-
ancestors.pop()
116-
117-
if (context.source.startsWith(`</${element.tag}`)) {
118-
// 解析结束标签
119-
parseTag(context, 'end')
120-
} else {
121-
console.error(`缺失结束标签:${element.tag}`)
122-
}
123-
124-
return element
125-
}
126-
127-
/**
128-
* 解析标签
129-
* @param {*} context
130-
* @param {*} type
131-
* @returns
132-
*/
133-
function parseTag(context, type = 'start') {
134-
const { source, advanceBy, advanceSpaces } = context
135-
// <div></div>
136-
// type=start: ['<div', 'div', index: 0, input: '<div>', groups: undefined]
137-
const match =
138-
type === 'start'
139-
? /^<([a-z][^\t\r\n\f />]*)/i.exec(source) // 匹配开始标签
140-
: /^<\/([a-z][^\t\r\n\f />]*)/i.exec(source) // 匹配结束标签
141-
const tag = match[1]
142-
143-
// 移除 <div
144-
advanceBy(match[0].length)
145-
// 移除多余空格
146-
advanceSpaces()
147-
148-
const props = parseAttributes(context)
3+
<script src="./static/mini-vue.umd.js"></script>
1494

150-
// 暂时不处理自闭合标签
151-
// 移除 >
152-
advanceBy(1)
153-
154-
return {
155-
type: NodeTypes.ELEMENT,
156-
tag,
157-
props,
158-
children: []
159-
}
160-
}
161-
162-
/**
163-
* 解析标签属性
164-
* @param {*} context
165-
* @returns {Array}
166-
* @example
167-
*
168-
* id="foo" class="bar"
169-
* =>
170-
* [{ type: 'Attribute', name: 'id', value: 'foo' }, { type: 'Attribute', name: 'class', value: 'bar' }]
171-
*/
172-
function parseAttributes(context) {
173-
const { advanceBy, advanceSpaces } = context
174-
const props = []
175-
176-
// example: id="foo" class="bar"></div>
177-
while (!context.source.startsWith('>') && !context.source.startsWith('/>')) {
178-
const match = /([\w:-@]+)=/.exec(context.source)
179-
// 属性名称
180-
let name = match[1]
181-
// 移除 id
182-
advanceBy(name.length)
183-
// 移除 =
184-
advanceBy(1)
185-
186-
let isStatic = true
187-
if (name.startsWith('@')) { // @click -> onClick
188-
isStatic = false
189-
const eventName = name.slice(1)
190-
name = 'on' + eventName[0].toUpperCase() + eventName.slice(1)
191-
}
192-
193-
let value = ''
194-
195-
const quote = context.source[0]
196-
const isQuoted = quote === '"' || quote === "'"
197-
if (isQuoted) {
198-
advanceBy(1)
199-
const endIndex = context.source.indexOf(quote)
200-
value = context.source.slice(0, endIndex)
201-
advanceBy(value.length)
202-
advanceBy(1)
203-
} else {
204-
// 处理非引号包裹的属性值
205-
}
206-
// 移除空格
207-
advanceSpaces()
208-
209-
props.push({
210-
type: NodeTypes.ATTRIBUTE,
211-
name,
212-
value,
213-
isStatic,
214-
})
215-
}
216-
217-
return props
218-
}
219-
220-
/**
221-
* 解析文本
222-
* @param {*} context
223-
* @returns
224-
* @examples
225-
*
226-
* case 1: template</div>
227-
* case 2: template {{ msg }}</div>
228-
* ...
229-
*/
230-
function parseText(context) {
231-
let endIndex = context.source.length
232-
const ltIndex = context.source.indexOf('<')
233-
const delimiterIndex = context.source.indexOf('{{')
234-
235-
if (ltIndex > -1 && ltIndex < endIndex) {
236-
endIndex = ltIndex
237-
}
238-
if (delimiterIndex > -1 && delimiterIndex < endIndex) {
239-
endIndex = delimiterIndex
240-
}
241-
242-
const content = context.source.slice(0, endIndex)
243-
244-
context.advanceBy(content.length)
245-
246-
return {
247-
type: NodeTypes.TEXT,
248-
content
249-
}
250-
}
251-
252-
/**
253-
* 是否解析结束
254-
* @param {*} context
255-
* @param {*} ancestors
256-
*/
257-
function isEnd(context, ancestors) {
258-
if (!context.source) return true
259-
// 与节点栈内全部的节点比较
260-
for (let i = ancestors.length - 1; i >= 0; --i) {
261-
if (context.source.startsWith(`</${ancestors[i].tag}`)) {
262-
return true
263-
}
264-
}
265-
}
5+
<script>
6+
const { parse } = MiniVue
2667

267-
const template = `<div id="foo" class="bar"><p>{{ msg }}</p><p>Template</p></div>`
8+
const template = `
9+
<div>
10+
<p>Template</p>
11+
</div>
12+
`
26813
const ast = parse(template)
26914

27015
console.log('开始解析:', template)

demo/21-compile.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
<script>
66
const { compileToFunction } = MiniVue
77

8-
const template = `<div id="foo" class="bar"><p>{{ msg }}</p><p>Template</p></div>`
8+
const template = `
9+
<div id="foo" class="bar">
10+
<p>{{ msg }}</p>
11+
<p>Template</p>
12+
</div>
13+
`
914
const render = compileToFunction(template)
1015

1116
console.log('模板:', template)
12-
console.log(render)
17+
console.log('渲染函数:', render)
1318
</script>
1419

demo/22-counter.html

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@
66
<title>Document</title>
77
<script src="./static/mini-vue.umd.js"></script>
88
<style>
9-
.red {
10-
color: red;
11-
}
129
.demo {
1310
position: absolute;
1411
top: 50%;
1512
left: 50%;
1613
transform: translate(-50%, -50%);
1714
}
1815
.count {
19-
margin: 0 10px;
16+
margin: 0 15px;
2017
}
2118
</style>
2219
</head>
2320
<body>
24-
<div id="app"><div class="demo"><button @click="minus">-1</button><span class="count">{{ count }}</span><button @click="plus">+1</button></div></div>
21+
<div id="app">
22+
<div class="demo">
23+
<button @click="minus">-1</button>
24+
<span class="count">{{ count }}</span>
25+
<button @click="plus">+1</button>
26+
</div>
27+
</div>
28+
2529
<script>
2630
console.log('MiniVue:', MiniVue)
2731
const { createApp, ref } = MiniVue

0 commit comments

Comments
 (0)