诗号:六道同坠,魔劫万千,引渡如来。

/img/bdx/yiyeshu-001.jpg

stb-vue-next 完全拷贝于 vue-next ,主要目的学习及尝试应用于机顶盒环境。

本文依据 commit 进程进行记录,只要跟着下面的进程走,你将能完整实 现 vue ast parser 哦 💃🏼💃🏼💃🏼

声明:该篇为 ts 源码(commit)版本,之前做过一遍完整的 js 版本,更详细,也可参考

Vue3.0 源码系列(二)编译器核心 - Compiler core 1: parse.ts - 若叶知秋

脑图

/img/vue3/compiler-core/compiler-core-parser.svg

compiler-core parser 初始化

Vue3.0 源码系列(二)编译器核心 - Compiler core 1: parse.ts

  • c0a03af add baseParse declaration

    feat(add): baseParse declaration · gcclll/stb-vue-next@c0a03af

    添加 baseParse() 函数声明:

    1
    2
    3
    
    export function baseParse(content: string, options: ParserOptions): RootNode {
        return {} as RootNode
    }
    
  • cb2d452 init baseParse function

    feat: baseParse function · gcclll/stb-vue-next@cb2d452

    增加 baseParse 函数实现,和涉及到的一些函数和类型声明。

    1
    2
    3
    4
    5
    6
    7
    8
    
    export function baseParse(content: string, options: ParserOptions): RootNode {
        const context = createParserContext(content, options)
        const start = getCursor(context)
        return createRoot(
            parseChildren(context, TextModes.DATA, []),
            getSelection(context, start)
        )
    }
    

4c6009d add pure text parser(parseText, parseTextData)

feat(add): parseText, parseTextData · gcclll/stb-vue-next@4c6009d

fix lint errors: 005c261

fix: lint errors · gcclll/stb-vue-next@005c261

新增了三个函数:

  1. pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void

    遍历 while 解析后的 ast ,合并相邻的文本节点。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
     function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {
         if (node.type === NodeTypes.TEXT) {
             // 合并两个相邻的文本内容
    
             const prev = last(nodes)
             // Merge if both this and the previous node are text and those are
             // consecutive. This happens for cases like "a < b".
             if (
             prev &&
             prev.type === NodeTypes.TEXT &&
             prev.loc.end.offset === node.loc.start.offset
             ) {
             prev.content += node.content
             prev.loc.end = node.loc.end
             prev.loc.source += node.loc.source
             return
             }
         }
    
         nodes.push(node)
     }
    
  2. parseText(context: ParserContext, mode: TextModes): TextNode

    解析文本节点,文本节点结束标识: <{{ ,分别代表标签和插值开始符号。

    如: some text<div>...., some text{{ ... }}

     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
    
     function parseText(context: ParserContext, mode: TextModes): TextNode {
         __TEST__ && assert(context.source.length > 0)
    
         const endTokens = ['<', context.options.delimiters[0]]
         if (mode === TextModes.CDATA) {
             endTokens.push(']]>')
         }
    
         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
             }
         }
    
         __TEST__ && assert(endIndex > 0)
    
         const start = getCursor(context)
         const content = parseTextData(context, endIndex, mode)
    
         return {
             type: NodeTypes.TEXT,
             content,
             loc: getSelection(context, start)
         }
     }
    
  3. function parseTextData(context: ParserContext, length: number, mode: TextModes): string

    处理 HTML 一些特殊符号,比如: a > b => a &lt; b

    1
    2
    3
    4
    5
    6
    7
    8
    
     const decodeRE = /&(gt|lt|amp|apos|quot);/g
     const decodeMap: Record<string, string> = {
         gt: '>',
         lt: '<',
         amp: '&',
         apos: "'",
         quot: '"'
     }
    

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

let ast = baseParse(`some text`)
console.log(`>>> 普通文本 "some text"`)
console.log(ast)
console.log(`>>> 带 html 语义符号的文本 "a &lt; b"`)
ast = baseParse(`a &lt; b`)
console.log(ast)

+RESULTS: 如结果显示 &lt;, &gt; 等符号会被转成语义化符号。

>>> 普通文本 "some text"
{
  type: 0,
  children: [ { type: 2, content: 'some text', loc: [Object] } ],
}
>>> 带 html 语义符号的文本 "a &lt; b"
{
  type: 0,
  children: [ { type: 2, content: 'a < b', loc: [Object] } ],
}
undefined

d7dbc28 add comment parser(parseComment)

feat(add): comment parser · gcclll/stb-vue-next@d7dbc28

修改 parseChildren():

else if s[0] === '<' 作为开始,可能是标签、html 注释等等。

http://qiniu.ii6g.com/img/20201124181448.png

代码:

 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
48
49
function parseComment(context: ParserContext): CommentNode {
  __TEST__ && assert(startsWith(context.source, '<!--'))

  const start = getCursor(context)
  let content: string

  const match = /--(\!)?>/.exec(context.source)

  if (!match) {
    // 非法注释
    content = context.source.slice(4)
    advanceBy(context, context.source.length)
    emitError(context, ErrorCodes.EOF_IN_COMMENT)
  } else {
    if (match.index <= 3) {
      // 不满足 <!-- -->
      emitError(context, ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT)
    }

    if (match[1]) {
      // 非法结束 <!-- --!>
      emitError(context, ErrorCodes.INCORRECTLY_CLOSED_COMMENT)
    }

    // 注释内容
    content = context.source.slice(4, match.index)

    // 嵌套注释
    const s = context.source.slice(0, match.index)
    let prevIndex = 1,
      nestedIndex = 0

    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
      advanceBy(context, nestedIndex - prevIndex + 1)
      if (nestedIndex + 4 < s.length) {
        emitError(context, ErrorCodes.NESTED_COMMENT)
      }
      prevIndex = nestedIndex + 1
    }

    advanceBy(context, match.index + match[0].length - prevIndex + 1)
  }

  return {
    type: NodeTypes.COMMENT,
    content,
    loc: getSelection(context, start)
  }
}
  1. 通过 /--(\!)?>/ 匹配注释的结束

  2. 如果无法匹配到,说明是非法注释,如: <!-- xxx ->

  3. 匹配到之后的非法情况(match.index <= 3): <!--><!--->

  4. 捕获组((\!))也匹配到了,非法结束: <!-- --!>

  5. 嵌套注释也视为非法

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

const catchError = fn => {
  try { fn() } catch (e) { console.log(e.message) }
}

let ast = baseParse(`<!-- xx -->`)
console.log(`>>> 非法注释:"<!-- xxx ->"`)
catchError( () => baseParse(`<!-- xxx ->`))
console.log(`>>> 非法注释:"<!--->"`)
catchError(  () => baseParse(`<!--->`))
console.log(`>>> 非法注释:"<!-- xx --!>"`)
catchError(  () => baseParse(`<!-- xx --!>`))
console.log(`>>> 嵌套注释:"<!-- <!-- -->"`)
catchError(  () => baseParse(`<!-- <!-- -->`))
console.log('>>> 有效注释')
console.log(ast)

+RESULTS:

>>> 非法注释:"<!-- xxx ->"
Unexpected EOF in comment.
>>> 非法注释:"<!--->"
Illegal comment.
>>> 非法注释:"<!-- xx --!>"
Incorrectly closed comment.
>>> 嵌套注释:"<!-- <!-- -->"
Unexpected '<!--' in comment.
>>> 有效注释
{
  type: 0,
  children: [ { type: 3, content: ' xx ', loc: [Object] } ],
  // ...
}

7d5f9c4 add bogus comment parser(parseBogusComment)

feat(add): bogus comment parser · gcclll/stb-vue-next@7d5f9c4

匹配正则: /^<(?:[\!\?]|\/[^a-z>])/i

http://qiniu.ii6g.com/img/20201124185249.png

  1. <!DOCTYPE 注释

  2. <![[CDATA> 类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function parseBogusComment(context: ParserContext): CommentNode | undefined {
  // <?... or <!... or </.... 形式注释 ???
  __TEST__ && assert(/^<(?:[\!\?]|\/[^a-z>])/i.test(context.source))

  const start = getCursor(context)
  const contentStart = context.source[1] === '?' ? 1 : 2
  let content: string

  // 结束
  const closeIndex = context.source.indexOf('>')
  if (closeIndex === -1) {
    content = context.source.slice(contentStart)
    advanceBy(context, context.source.length)
  } else {
    content = context.source.slice(contentStart, closeIndex)
    advanceBy(context, closeIndex + 1)
  }

  return {
    type: NodeTypes.COMMENT,
    content,
    loc: getSelection(context, start)
  }
}

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

const catchError = fn => {
  let res
  try { res = fn() } catch (e) { console.log(e.message) }
  res && console.log(res)
}

// html 中使用 <![CDATA[ 注释
catchError(() => baseParse(`<![CDATA[ xxx ]]`))
catchError(() => baseParse(`<!DOCTYPE xxx >`))

+RESULTS:

CDATA section is allowed only in XML context.
{
  type: 0,
  children: [ { type: 3, content: 'DOCTYPE xxx ', loc: [Object] } ],
}

cef8485 add more error element situations

feat(add): more error element situations · gcclll/stb-vue-next@cef8485

更多错误标签情况,以 </ 开头的情况处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

const catchError = fn => {
  let res
  try { res = fn() } catch (e) { console.log(e.message) }
  res && console.log(res)
}

catchError(() => baseParse(`</`))
catchError(() => baseParse(`</>`))
catchError(() => baseParse(`</xx>`))
catchError(() => baseParse(`<?`))
catchError(() => baseParse(`<*`))

+RESULTS:

Unexpected EOF in tag.
End tag name was expected.
Invalid end tag.
'<?' is allowed only in XML context.
Illegal tag name. Use '&lt;' to print '<'.

b8cb825 add interpolation parser

feat(add): interpolation parser · gcclll/stb-vue-next@b8cb825

插值解析。

 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
function parseInterpolation(
  context: ParserContext,
  mode: TextModes
): InterpolationNode | undefined {
  const [open, close] = context.options.delimiters
  __TEST__ && assert(startsWith(context.source, open))

  const closeIndex = context.source.indexOf(close, open.length)
  if (closeIndex === -1) {
    emitError(context, ErrorCodes.X_MISSING_INTERPOLATION_END)
    return undefined
  }

  const start = getCursor(context)
  advanceBy(context, open.length)
  const innerStart = getCursor(context)
  const innerEnd = getCursor(context)
  // 插值内容长度
  const rawContentLength = closeIndex - open.length
  const rawContent = context.source.slice(0, rawContentLength)
  // html 语义化符号替换
  const preTrimContent = parseTextData(context, rawContentLength, mode)
  // 去掉前后空格
  const content = preTrimContent.trim()
  // 去掉空格后的内容所在的索引位置
  const startOffset = preTrimContent.indexOf(content)
  if (startOffset > 0) {
    advancePositionWithMutation(innerStart, rawContent, startOffset)
  }

  const endOffset =
    rawContentLength - (preTrimContent.length - content.length - startOffset)
  advancePositionWithMutation(innerEnd, rawContent, endOffset)
  advanceBy(context, close.length)

  return {
    type: NodeTypes.INTERPOLATION,
    content: {
      type: NodeTypes.SIMPLE_EXPRESSION,
      isStatic: false,
      isConstant: false,
      content,
      loc: getSelection(context, innerStart, innerEnd)
    },
    loc: getSelection(context, start)
  }
}

执行操作:

  1. 根据 {{, }} 取出插值起始索引

  2. 截取插值内容,替换 html 语义字符,且去掉前后空格

  3. 组装插值结构

1
2
3
4
5
6
7
8
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

const ast = baseParse(`{{ foo.value }}`)
console.log(ast)
console.log(`>>> 插值节点`)
console.log(ast.children[0])

+RESULTS:

{
  type: 0,
  children: [ { type: 5, content: [Object], loc: [Object] } ],
}
>>> 插值节点
{
  type: 5,
  content: {
    type: 4,
    isStatic: false,
    isConstant: false,
    content: 'foo.value',
    loc: { start: [Object], end: [Object], source: 'foo.value' }
  },
  loc: {
    start: { column: 1, line: 1, offset: 0 },
    end: { column: 16, line: 1, offset: 15 },
    source: '{{ foo.value }}'
  }
}
undefined

397da38 add element parser

feat(add): parse element function · gcclll/stb-vue-next@397da38

解析元素标签的入口函数,实际详细解析在 parseTag() 函数中,所以这里需要结合 parseTag 的实现才能测试。

代码:

 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
48
49
50

function parseElement(
  context: ParserContext,
  ancestors: ElementNode[]
): ElementNode | undefined {
  __TEST__ && assert(/^<[a-z]/i.test(context.source))

  const wasInPre = context.inPre
  const wasInVPre = context.inVPre
  const parent = last(ancestors)
  // 解析出开始标签
  const element = {} as any // parseTag(context, TagType.Start, parent)
  const isPreBoundray = context.inPre && !wasInPre
  const isVPreBoundray = context.inVPre && !wasInVPre

  if (element.isSelfClosing || context.options.isVoidTag(elment.tag)) {
    return element
  }

  ancestors.push(element)
  const mode = context.options.getTextMode(element, parent)
  const children = parseChildren(context, mode, ancestors)
  // 要将孩子节点解析完成的 parent element pop 掉,待处理下一个 parent 的 children
  ancestors.pop()

  if (startsWithEndTagOpen(context.source, element.tag)) {
    // 结束标签
    // parseTag(context, TagType.End, parent)
  } else {
    emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)
    if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
      const first = children[0]
      if (first && startsWith(first.loc.source, '<!--')) {
        emitError(context, ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT)
      }
    }
  }

  element.loc = getSelection(context, element.loc.start)

  if (isPreBoundray) {
    context.inPre = false
  }

  if (isVPreBoundray) {
    context.inVPre = false
  }

  return element
}

源码分析:

  1. 通过调用 parseTag() 解析出标签元素结构

  2. 判断是不是自闭合标签(<div/>),或者外部定义的空标签(不需要结束标签的,如: <my-tag> ,为合法标签)

  3. 调用 parseChildren() 递归解析该节点下子孙节点

  4. 结束标签解析

  5. <pre>v-pre 检测

3b96a74 add tag parser

feat(add): tag parser · gcclll/stb-vue-next@3b96a74

 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63


function parseTag(
  context: ParserContext,
  type: TagType,
  parent: ElementNode | undefined
): ElementNode {
  // 匹配 <div> 或 </div>
  __TEST__ && assert(/^<\/?[a-z]/i.test(context.source))
  __TEST__ &&
    assert(
      type === (startsWith(context.source, '</') ? TagType.End : TagType.Start)
    )

  // 开始标签
  const start = getCursor(context)
  const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
  const tag = match[1]
  const ns = context.options.getNamespace(tag, parent)

  advanceBy(context, match[0].length)
  advanceSpaces(context)

  // 保存当前状态,待会需要回过头来解析属性
  // const cursor = getCursor(context)
  // const currentSource = context.source

  // 解析属性
  let props = [] as any[] // TODO parseAttributes(context, type)

  // TODO <pre> 标签

  // TODO v-pre 指令

  // 结束标签
  let isSelfClosing = false
  if (context.source.length === 0) {
    emitError(context, ErrorCodes.EOF_IN_TAG)
  } else {
    // <div ... />
    isSelfClosing = startsWith(context.source, '/>')
    // 到这里不应该是 End 标签
    if (type === TagType.End && isSelfClosing) {
      emitError(context, ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS)
    }
    advanceBy(context, isSelfClosing ? 2 : 1)
  }

  let tagType = ElementTypes.ELEMENT

  // TODO 标签类型解析
  return {
    type: NodeTypes.ELEMENT,
    ns,
    tag,
    tagType,
    props,
    isSelfClosing,
    children: [],
    loc: getSelection(context, start),
    codegenNode: undefined
  }
}
  1. 开始标签匹配正则: /^<\/?([a-z][^\t\r\n\f />]*)/i

    http://qiniu.ii6g.com/img/20201124230909.png

  2. <pre> 标签处理

  3. v-pre 指令处理

  4. 自闭合标签处理

  5. 组装元素结构 NodeTypes.ELEMENT

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

let ast = baseParse(`<div></div>`)
console.log(`>>> 普通标签`)
console.log(ast.children[0])
ast = baseParse(`<img/>`)
console.log(`>>> 自闭合标签`)
console.log(ast.children[0])
console.log(`>>> 自定义空标签 <mydiv>`)
ast = baseParse(`<mydiv>`, {
  isVoidTag: () => `mydiv`
})
console.log(ast.children[0])

+RESULTS: 省略部分输出

>>> 普通标签
{
  type: 1,
  ns: 0,
  tag: 'div',
  tagType: 0,
  props: [],
  isSelfClosing: false,
  children: [],
}
>>> 自闭合标签
{
  type: 1,
  ns: 0,
  tag: 'img',
  tagType: 0,
  props: [],
  isSelfClosing: true,
  children: [],
}
>>> 自定义空标签 <mydiv>
{
  type: 1,
  ns: 0,
  tag: 'mydiv',
  tagType: 0,
  props: [],
  isSelfClosing: false,
  children: [],
}

bf28a36 add tag parser of tag type

feat(add): parse tag for tag type · gcclll/stb-vue-next@bf28a36

解析出标签的标签名(component ? template ? slot ? …)。

  1. if (options.isNativeTag && !hasVIs)

    !options.isNativeTag(tag) 如果不是原生标签,则视为 COMPONENT

  2. 第二种为 COMPONENT 情况

    1
    2
    3
    4
    5
    6
    7
    
     else if (
       hasVIs ||
       isCoreComponent(tag) ||
       (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
       /^[A-Z].test(tag)/ ||
       tag === 'component'
     )
    
    • v-is 指令

    • isCoreComponent() vue 内置标签(Teleport, Suspense, KeepAlive, BaseTransition)

    • 选项中自定义的

    • 标签名首字母大写的也视为 component

    • 标签名直接是 component

  3. if (tag === 'slot') 插槽标签

  4. <template> 标签,且带有指令

    1
    2
    3
    4
    5
    6
    
     tag === 'template' &&
       props.some(p => {
         return (
           p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
         )
       })
    

    特殊的模板指令:

    1
    2
    3
    
     const isSpecialTemplateDirective = /*#__PURE__*/ makeMap(
         `if,else,else-if,for,slot`
     )
    

这个由于需要用到属性,所以需要结合 parseAttributes 实现才能进行测试。

73fd01f add attribute name and value parser

feat(add): attribute name and value parser · gcclll/stb-vue-next@73fd01f

这里新增了三个函数(代码较多,需要查看源码直接点击上面 commit 链接)

  1. parseAttributes(context, type) 属性解析入口,通过 while 循环解析出所有属性

  2. parseAttribute(context, nameSet) 解析单个属性,属性名用 nameSet 集合存储避 免重复

  3. parseAttributeValue(context) 解析属性值

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

const ast = baseParse(`<div class="app" :staticPropName="bar" @press.enter="pressKey" :[dynamicPropName]="foo"></div>`)
const ele = ast.children[0]
console.log(ele)
console.log(`>>> 静态属性:class`)
console.log(ele.props[0])
console.log(`>>> 动态属性静态属性名:staticPropName`)
console.log(ele.props[1])
console.log(`>>> 带修饰符的属性:press.enter`)
console.log(ele.props[2])
console.log(`>>> 动态属性名:dynamicPropName`)
console.log(ele.props[3])

+RESULTS: 元素结构

{
  type: 1,
  ns: 0,
  tag: 'div',
  tagType: 1,
  props: [...], // 如下
  isSelfClosing: false,
  children: [],
}

+RESULTS: 属性列表, 省略 loc 位置数据

>>> 静态属性:class
{
  type: 6,
  name: 'class',
  value: {
    type: 2,
    content: 'app',
  }
}
>>> 动态属性静态属性名:staticPropName
{
  type: 7,
  name: 'bind',
  exp: {
    type: 4,
    content: 'bar',
    isStatic: false,
    isConstant: false,
  },
  arg: {
    type: 4,
    content: 'staticPropName',
    isStatic: true,
    isConstant: true,
  },
  modifiers: [],
}
>>> 带修饰符的属性:press.enter
{
  type: 7,
  name: 'on',
  exp: {
    type: 4,
    content: 'pressKey',
    isStatic: false,
    isConstant: false,
  },
  arg: {
    type: 4,
    content: 'press',
    isStatic: true,
    isConstant: true,
  },
  modifiers: [ 'enter' ],
}
>>> 动态属性名:dynamicPropName
{
  type: 7,
  name: 'bind',
  exp: {
    type: 4,
    content: 'foo',
    isStatic: false,
    isConstant: false,
  },
  arg: {
    type: 4,
    content: 'dynamicPropName',
    isStatic: false,
    isConstant: false,
  },
  modifiers: [],
}

e32401e add combine whitespace nodes

feat(add): combine whitespace node · gcclll/stb-vue-next@e32401e

合并删除空行或空字符串节点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

const ast = baseParse(`
<div>
some text
other text
</div>`)
console.log(ast.children[0].children[0])

+RESULTS: 正确结果

{
  type: 2,
  content: ' some text other text ',
  loc: {
    start: { column: 6, line: 2, offset: 6 },
    end: { column: 1, line: 5, offset: 28 },
    source: '\nsome text\nother text\n'
  }
}

+RESULTS: 'sometextothertext' 空格都被删了? fix: all whitespce removed · gcclll/stb-vue-next@bb31509

{
  type: 1,
  ns: 0,
  tag: 'div',
  tagType: 1,
  props: [],
  isSelfClosing: false,
  children: [ { type: 2, content: 'sometextothertext', loc: [Object] } ],
  loc: {
    start: { column: 1, line: 2, offset: 1 },
    end: { column: 7, line: 5, offset: 34 },
    source: '<div>\nsome text\nother text\n</div>'
  },
  codegenNode: undefined
}
undefined

+RESULTS: children = [] ? fix: no children · gcclll/stb-vue-next@66936f3

{
  type: 1,
  ns: 0,
  tag: 'div',
  tagType: 1,
  props: [],
  isSelfClosing: false,
  children: [],
  loc: {
    start: { column: 1, line: 2, offset: 1 },
    end: { column: 7, line: 5, offset: 34 },
    source: '<div>\nsome text\nother text\n</div>'
  },
  codegenNode: undefined
}
undefined

用例测试

<f12> 打开控制台有惊喜哦╰(°▽°)╯ 👀👀👀👀👀👀👀👀。

下面章节所有测试都是根据官方测试用例进行的:parse.spec.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const {
  baseParse
} = require(process.env.PWD + '/../../static/js/vue/compiler-core.global.js')

const _ = x => console.log(`>>> ${x}`)
const __ = x => console.log(x)
const parse = content => baseParse(content, {
  onError: (e) => console.log(e.message)
})

__(parse(`some text`).children[0])

更多测试内容和输出(由于篇幅问题)请查看 <F12> 打开控制台查看。 +RESULTS:

{
  type: 2,
  content: 'some text',
  loc: {
    start: { column: 1, line: 1, offset: 0 },
    end: { column: 10, line: 1, offset: 9 },
    source: 'some text'
  }
}
undefined