“This is the 8th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

preface

In the previous article, we looked at part of the parse code during compilation, which is the implementation of parseHTML. The template is initially parsed in parseHTML by word-for-word matching. Now let’s move on to analyzing how the output in parseHTML is used by Parse. This completes the analysis of the complete parse process.

parse

We still start with the import file

const ast = parse(template.trim(), options)
Copy the code

Let’s look at the implementation of Parse

const stack = []
let root
let currentParent

parseHTML(template, {
  expectHTML: options.expectHTML,
  // ...
  start (tag, attrs, unary, start, end) {
    // Parse the hook called by the start tag
  },

  end (tag, start, end) {
    // Parse the hook called by the closing tag
  },

  chars (text: string, start: number, end: number) {
    // Parse the hook called by the text node
  },

  comment (text: string, start, end) {
    // Parse the hook called by the annotation node}})return root
Copy the code

As you can see, parse’s implementation basically initializes some hook functions and passes them as arguments to parseHTML. We looked at the implementation of parseHTML, which extracts the teamplate’s information through regex, extracts its tag attributes, and then calls the hooks in Parse. So the focus of this article is on further processing in hook functions.

Let’s do it by example

<div>
  <div v-if="isShow" @click="doSomething" :class="activeClass">text{{name}}{{value}}text</div>
  <div v-for="item in 10"></div>
</div>
Copy the code

start

start (tag, attrs, unary, start, end) {
  / / 1
  let element: ASTElement = createASTElement(tag, attrs, currentParent)

  / / 2
  for (let i = 0; i < preTransforms.length; i++) {
    element = preTransforms[i](element, options) || element
  }

  // ...

  / / 3
  if (inVPre) {
    processRawAttrs(element)
  } else if(! element.processed) {// structural directives
    processFor(element)
    processIf(element)
    processOnce(element)
  }

  // ...

  / / 4
  if(! unary) { currentParent = element stack.push(element) }else {
    closeElement(element)
  }
}
Copy the code

The simplified SATRT is not complicated. Let’s sort out its implementation

  1. Create a node AST from label and attribute data
{
  type: 1,
  tag,
  attrsList: attrs,
  attrsMap: makeAttrsMap(attrs),
  rawAttrsMap: {},
  parent,
  children: []}Copy the code
  1. Execute functions exposed in preTransforms, which are collections of functions associated with modules in baseOptions, similar to the node updates in vUE rendering analyzed earlier.

  2. Different process functions are executed, and as you can see from the function name, process is used to further process instructions such as for, if, and so on.

  3. We defined the stack to hold the stack of nodes currently created, push it in after creation, and point currentParent to the node.

For a single node, let’s look at the data comparison before and after start

end

 const element = stack[stack.length - 1]
  // pop stack
  stack.length -= 1
  currentParent = stack[stack.length - 1]
  closeElement(element)
Copy the code

The main code for end is simply to push out the node just pushed in start and update currentParent to indicate that the current node label is closed and processing is complete. CloseElement does some extra checks and calls and so on, so no analysis here.

chars

chars (text: string, start: number, end: number) {
  const children = currentParent.children
  / /...

  if (text) {
    let res
    letchild: ? ASTNodeif(! inVPre && text ! = =' ' && (res = parseText(text, delimiters))) {
      child = {
        type: 2.expression: res.expression,
        tokens: res.tokens,
        text
      }
    }

    if (child) {
      children.push(child)
    }
  }
}
Copy the code

Chars is used to process text information, mainly by calling parseText to parse the string in the text and extract the variables. Let’s look at the implementation

export function parseText (text: string, delimiters? : [string, string]) :TextParseResult | void {
  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
  if(! tagRE.test(text)) {return
  }

  / / 1
  const tokens = []
  const rawTokens = []
  let lastIndex = tagRE.lastIndex = 0
  let match, index, tokenValue

  / / 2
  while ((match = tagRE.exec(text))) {
    index = match.index
    // push text token
    if (index > lastIndex) {
      rawTokens.push(tokenValue = text.slice(lastIndex, index))
      tokens.push(JSON.stringify(tokenValue))
    }
    // tag token
    const exp = parseFilters(match[1].trim())
    tokens.push(`_s(${exp}) `)
    rawTokens.push({ '@binding': exp })
    lastIndex = index + match[0].length
  }

  / / 3
  if (lastIndex < text.length) {
    rawTokens.push(tokenValue = text.slice(lastIndex))
    tokens.push(JSON.stringify(tokenValue))
  }

  / / 4
  return {
    expression: tokens.join('+'),
    tokens: rawTokens
  }
}
Copy the code

The implementation of parseText is also not complicated, a bit like parseHTML, in that it actually tokenizes text nodes further

  1. Defines output variables as well as temporary variables needed to traverse text

  2. The loop matches the text, extracting the traversal by matching {{}}(of course, passing other delimiters will be different), and determining whether there are strings in front of it by matching the position, extracting all together. Of course matching variables add _s(). Don’t think twice that _s() is actually the function that will be executed later when used for rendering.

  3. Do the closing, that is, the XXX text in the case of {{name}} XXXX.

  4. Return the extracted expression and token, and let’s see the difference between the input and output values.

comment

Finally, let’s take a look at the annotation node processing, which is very simple, using the corresponding node variable to store text

comment (text: string, start, end) {
  if (currentParent) {
    const child: ASTText = {
      type: 3,
      text,
      isComment: true
    }
    currentParent.children.push(child)
  }
}
Copy the code

other

We looked at the main processes of Parse earlier, and it didn’t feel complicated. The actual parse contains a lot of content because we skip a lot of instruction processing logic such as V-if, V-for, V-pre, V-slot, V-ESle, V-elseif, V-Model, etc. Their processing logic is mainly in their respective process functions. We take V-for as an example to analyze their processing

processFor

export function processFor (el: ASTElement) {
  let exp
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    const res = parseFor(exp)
    if (res) {
      extend(el, res)
    }
  }
}
Copy the code

The primary logic of processFor is to determine the presence of a V-for instruction by the attrsMap of the node and, if so, to parse its value. Merge the parsing results into element.

Let’s look at parseFor

export function parseFor (exp: string): ?ForParseResult {
  const inMatch = exp.match(forAliasRE)
  if(! inMatch)return
  const res = {}
  res.for = inMatch[2].trim()
  const alias = inMatch[1].trim().replace(stripParensRE, ' ')
  const iteratorMatch = alias.match(forIteratorRE)
  if (iteratorMatch) {
    res.alias = alias.replace(forIteratorRE, ' ').trim()
    res.iterator1 = iteratorMatch[1].trim()
    if (iteratorMatch[2]) {
      res.iterator2 = iteratorMatch[2].trim()
    }
  } else {
    res.alias = alias
  }
  return res
}
Copy the code

The logic of parseFor actually parses the developer-defined items such as item in List, splits it up and returns its object, which is stored in properties such as alias and for.

Let’s look at the nodes before and after the processing

AST

Let’s take a look at the AST code that our template eventually generated

<div>
  <div v-if="isShow" @click="doSomething" :class="activeClass">text{{name}}{{value}}text</div>
  <div v-for="item in 10"></div>
</div>
Copy the code
const ast = parse(template.trim(), options)
Copy the code

conclusion

This article examines the first step in vUE compilation, compiling the Template into an AST. Compared with Babel, the AST generation of JS code is actually much simpler. The reason is that the parsing of templates is mainly to match tags and attributes in order, while the parsing of code has to consider a lot of things, especially to consider the processing of syntax logic. We will continue to examine the AST transformation in the second step of compilation.