Vue source code compilation optimize s AST tree

Three source optimization tree

The learning content and article content are from Mr. Huang Yi
Mr. Huang Yi's muke.com video tutorial address: Uncover the source code of Vue.js2.0,
Address of teacher Huang Yi's hook education course: Vue.js 3.0 core source code analysis
The source code analyzed here is Vue of Runtime + Compiler js
Debugging code: node_modules\vue\dist\vue.esm.js
vue version: vue js 2.5.17-beta

The more you live, the better your life will be. -- Frank Lloyd Wright
Classic quotations from the fruit of life

Click back to Vue source code to learn the complete directory

Vue source code compilation (I) compilation entry
The compiled entry we found mainly executes the following logic:

  • Parse template string to generate AST
const ast = parse(template.trim(), options)
  • Optimize syntax tree
optimize(ast, options)
  • Generate code
const code = generate(ast, options)

optimize

After the parse process, our template will be output to generate an AST tree. Then we need to optimize the tree. The logic of optimize is much simpler than that of parse, so it will be much easier to understand.

Why there should be an optimization process? We know that Vue is data-driven and responsive, but not all data in our template are responsive. There are also many data that will never change after the first rendering, so the DOM generated by this part of the data will not change. We can skip the comparison of them in the process of patch.

Let's take a look at the definition of the optimize method in Src / compiler / optimizer JS:

/**
 * Goal of the optimizer: walk the generated template AST tree
 * and detect sub-trees that are purely static, i.e. parts of
 * the DOM that never needs to change.
 *
 * Once we detect these sub-trees, we can:
 *
 * 1. Hoist them into constants, so that we no longer need to
 *    create fresh nodes for them on each re-render;
 * 2. Completely skip them in the patching process.
 */
export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  isPlatformReservedTag = options.isReservedTag || no
  // first pass: mark all non-static nodes.
  markStatic(root)
  // second pass: mark static roots.
  markStaticRoots(root, false)
}

function genStaticKeys (keys: string): Function {
  return makeMap(
    'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
    (keys ? ',' + keys : '')
  )
}

We can optimize some AST nodes into static nodes in the compilation stage, so the whole optimization process actually does two things: Mark static (root) marks static nodes and mark static roots (root, false) marks static roots.

Mark static nodes

function markStatic (node: ASTNode) {
  node.static = isStatic(node)
  if (node.type === 1) {
    // do not make component slot content static. this avoids
    // 1. components not able to mutate slot nodes
    // 2. static slot content fails for hot-reloading
    if (
      !isPlatformReservedTag(node.tag) &&
      node.tag !== 'slot' &&
      node.attrsMap['inline-template'] == null
    ) {
      return
    }
    for (let i = 0, l = node.children.length; i < l; i++) {
      const child = node.children[i]
      markStatic(child)
      if (!child.static) {
        node.static = false
      }
    }
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        const block = node.ifConditions[i].block
        markStatic(block)
        if (!block.static) {
          node.static = false
        }
      }
    }
  }
}

function isStatic (node: ASTNode): boolean {
  if (node.type === 2) { // expression
    return false
  }
  if (node.type === 3) { // text
    return true
  }
  return !!(node.pre || (
    !node.hasBindings && // no dynamic bindings
    !node.if && !node.for && // not v-if or v-for or v-else
    !isBuiltInTag(node.tag) && // not a built-in
    isPlatformReservedTag(node.tag) && // not a component
    !isDirectChildOfTemplateFor(node) &&
    Object.keys(node).every(isStaticKey)
  ))
}

First execute node static = isStatic(node)

isStatic is to judge whether an AST element node is static. If it is an expression, it is non static; If it is plain text, it is static; For an ordinary element, if it has a pre attribute, it uses the v-pre instruction, which is static. Otherwise, it must meet the following conditions at the same time: it does not use v-if, v-for, other instructions (excluding v-once), is not a built-in component, is a label reserved by the platform, is not a direct child node with a v-for template label, and the keys of all attributes of the node meet the static key; If all these are satisfied, the AST node is a static node.

If this node is an ordinary element, it will traverse all its children and execute markStatic recursively. Because all else if and else nodes are not in children, if the ifConditions of the node are not empty, traverse the ifConditions to get the block s in all conditions, that is, their corresponding AST nodes, and recursively execute markStatic. In these recursive processes, once the child node is not static, the static of its parent node will become false.

Mark static root

function markStaticRoots (node: ASTNode, isInFor: boolean) {
  if (node.type === 1) {
    if (node.static || node.once) {
      node.staticInFor = isInFor
    }
    // For a node to qualify as a static root, it should have children that
    // are not just static text. Otherwise the cost of hoisting out will
    // outweigh the benefits and it's better off to just always render it fresh.
    if (node.static && node.children.length && !(
      node.children.length === 1 &&
      node.children[0].type === 3
    )) {
      node.staticRoot = true
      return
    } else {
      node.staticRoot = false
    }
    if (node.children) {
      for (let i = 0, l = node.children.length; i < l; i++) {
        markStaticRoots(node.children[i], isInFor || !!node.for)
      }
    }
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        markStaticRoots(node.ifConditions[i].block, isInFor)
      }
    }
  }
}

The second parameter of markStaticRoots is isinfor. For nodes that are already static or v-once instructions, node staticInFor = isInFor.
Then there is the judgment logic of staticRoot. From the comments, we can see that for a node qualified to become staticRoot, in addition to being a static node, it must have children, and children cannot be just a text node. Otherwise, the benefit of marking it as a static root node is very small.

Next, like the logic of marking static nodes, traverse children and ifConditions, and recursively execute markStaticRoots.

Returning to our previous example, after optimizing, the AST tree becomes as follows:

ast = {
  'type': 1,
  'tag': 'ul',
  'attrsList': [],
  'attrsMap': {
    ':class': 'bindCls',
    'class': 'list',
    'v-if': 'isShow'
  },
  'if': 'isShow',
  'ifConditions': [{
    'exp': 'isShow',
    'block': // ul ast element
  }],
  'parent': undefined,
  'plain': false,
  'staticClass': 'list',
  'classBinding': 'bindCls',
  'static': false,
  'staticRoot': false,
  'children': [{
    'type': 1,
    'tag': 'li',
    'attrsList': [{
      'name': '@click',
      'value': 'clickItem(index)'
    }],
    'attrsMap': {
      '@click': 'clickItem(index)',
      'v-for': '(item,index) in data'
     },
    'parent': // ul ast element
    'plain': false,
    'events': {
      'click': {
        'value': 'clickItem(index)'
      }
    },
    'hasBindings': true,
    'for': 'data',
    'alias': 'item',
    'iterator1': 'index',
    'static': false,
    'staticRoot': false,
    'children': [
      'type': 2,
      'expression': '_s(item)+":"+_s(index)'
      'text': '{{item}}:{{index}}',
      'tokens': [
        {'@binding':'item'},
        ':',
        {'@binding':'index'}
      ],
      'static': false
    ]
  }]
}

We found that each AST element node has a static attribute, and the ordinary AST element node with type 1 has a staticRoot attribute.

summary

So far, we have analyzed the optimization process, that is, we deeply traverse the AST tree to detect whether each subtree is a static node. If it is a static node, they will never need to change the DOM generated, which plays a great role in optimizing the update of the template at run time.

By optimizing, we mark each AST element node in the whole AST tree with static and staticRoot, which will affect the next code generation process.

Vue source code learning directory

Componentization (I) createComponent
Componentization (II) patch
Componentization (III) combined configuration
Componentization (IV) life cycle
Componentization (V) component registration

In depth responsive principle (I) responsive object
In depth responsive principle (II) relying on collection & distribution of updates
In depth response principle (III) nexttick & Precautions for detecting changes
Deep response principle (IV) calculation attribute VS listening attribute
Deep response principle (V) deep response principle (V) component update
Deep response principle (VI) Props (v2.6.11)
In depth response principle (VII) schematic diagram summary

Compilation (I) compilation entry
Compile (II) parse parse template string to generate AST

Click back to Vue source code to learn the complete directory

Thank you for reading to the end~
We look forward to your attention, collection, comments and likes~
Make us stronger together

Tags: Vue Optimize

Posted by PatelNehal on Sun, 03 Apr 2022 03:44:15 +0300