This is the 29th day of my participation in the First Challenge 2022.

Previously on & Background

Parse’s next step after generating an AST is generate, which turns the AST into a rendering function code string. The core method of this process is generate(), and the core method of generate is the genElment method. The first step of the genElement method is to process the static root node, that is, the genStatic method. Promote the static render function to the staticRenderFns array;

The genData() method, which was described earlier and is called to take precedence of the directives, is again a small detail of understanding Vue two-way data binding, to which the length of this tutorial will be directed.

Second, the genDirectives

Methods location: SRC/compiler/codegen/index. The js – > function genDirectives

The method parameters

  1. el.astNode object;
  2. state:CodegenStateInstance objects

Methods: GenDirectives () for the compilation of directives, which is to use the previously described directive processing methods for directives on the platform, such as V-HTML, V-Model, etc., for the Web platform. Directives are mounted to the CodegenState instance when it is initialized. This. directives = extend(extend({}, baseDirectives), options.directives) The state directives

function genDirectives (el: ASTElement, state: CodegenState) :string | void {
  // Get the instruction array
  const dirs = el.directives

  // Exit if there is no command
  if(! dirs)return

  // The processing result of the instruction looks like this. This is because genDirectives are invoked by genData,
  // Return a key of the data object,
  // data = '{';
  // dirs = genDirectives(); 
  // data += dirs + ','; Data looks like this {direcitves: [....] .
  let res = 'directives:['

  // flags whether directives require run-time coordination, such as v-model input events, which are part of the run-time coordination
  let hasRuntime = false

  // Iterate over the instruction array
  let i, l, dir, needRuntime
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    // Get the processing method of the node's current instruction, such as v-html, V-text, V-model for the Web platform
    // Where are the state-cache part? Initialize CodegenState
    const gen: DirectiveFunction = state.directives[dir.name]
    if (gen) {
      // Execute the instruction's processing method, and return true if the instruction also needs runtime coordination, such as v-model
      // compile-time directive that manipulates AST.
      // returns true if it also needs a runtime counterpart.needRuntime = !! gen(el, dir, state.warn) }if (needRuntime) {
      // Concatenate the returned res for directives that need runtime processing with an object: {name, rawName, arg, modiFIERS}
      hasRuntime = true
      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
        dir.value ? `,value:(${dir.value}),expression:The ${JSON.stringify(dir.value)}` : ' '
      }${
        dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ' '
      }${
        dir.modifiers ? `,modifiers:The ${JSON.stringify(dir.modifiers)}` : ' '
      }}, `}}if (hasRuntime) {
    [{name, rawName, arg, modifiers}]
    return res.slice(0, -1) + '] '}}Copy the code

2.1 state. Directives

PluckModuleFunction extracts preTransformNode, transformNode, and postTransformNode methods from options.modules for processing Ast Node object. Options are baseOptions passed when the compiler is created;

This options.directives is also from baseoptions. directives; We used a single command to implement a complex function during the development of the Vue project, and it was easy to use because the framework was carrying the load. Native directives such as V-model/V-text/V-HTML require compile-time support and even runtime + compile-time collaboration;

  • Module location:src/platforms/web/compiler/options.js
import directives from './directives/index'

// src/platform/web/compiler/options.js
export const baseOptions: CompilerOptions = {
  expectHTML: true,
  modules, // Handle class, style, v-module
  directives, // How to process instructions
  / /...
  staticKeys: genStaticKeys(modules)
}
Copy the code
  • directivesFrom this module:src/platforms/web/compiler/directives/index.js
import model from './model'
import text from './text'
import html from './html'

export default {
  model, / / processing v - model
  text, / / v - text processing
  html / / processing v - HTML
}
Copy the code

2.2 V-model and Model module

Methods location: SRC/platforms/web/compiler directives/model. The js – > function model

Method parameters:

  1. el.Ast node object
  2. dir:astInstructions used on nodes and their details; From the abovegenDirectivesIt can be seen thatdirel.directivesThe item,el.directivesThe term is the frontparseHTMLCalled when theprcessAttrsCalled when theaddDirective()Adding instructions to a node;
  3. _warn: Warning message Prompt method

Methods: Get the details of the instruction, handle the select, input, and input tags of different types according to the case, and bind the v-Model elements with different event handlers in the runtime code to achieve different types of bidirectional binding; It can be divided into the following steps:

  1. Get directive details from dir: directive bound value, modifier modifier, tag name, and type;

  2. To handle the case where el is a custom component and v-Model is used, call genComponentModel

  3. The el is handled by the bidirectional binding of SELECT, which calls genSelect

  4. Handles bidirectional binding of input type = radio

  5. Handle the default input type values text and textarea, which is our most common case, by calling the genDefaultModel method.

  6. If el.tag is not a platform reserved tag, it is treated as if it were a custom component, calling genComponentModel, return false;

  7. Finally, the return value of this method identifies whether the current V-Model requires runtime coordination. This will affect the return of the genDirectives() method above;

export default function model (
 el: ASTElement,
 dir: ASTDirective,
 _warn: Function
): ?boolean {
 warn = _warn
 const value = dir.value
 const modifiers = dir.modifiers
 const tag = el.tag
 const type = el.attrsMap.type

 if(process.env.NODE_ENV ! = ='production') {
   // Input type = file Read-only, cannot write, warning
 }

 if (el.component) {
   // Handles the v-model of a custom component, which is implemented by the component definer himself
   // So it does not require additional runtime assistance from the framework
   genComponentModel(el, value, modifiers)
   // component v-model doesn't need extra runtime
   return false
 } else if (tag === 'select') {
   // Handle the V-model of the select tag
   genSelect(el, value, modifiers)
 } else if (tag === 'input' && type === 'checkbox') {
   // Handle the checkbox v-model
   genCheckboxModel(el, value, modifiers)
 } else if (tag === 'input' && type === 'radio') {
   // Handle radio V-mdel
   genRadioModel(el, value, modifiers)
 } else if (tag === 'input' || tag === 'textarea') {
   // By default  v-model
   genDefaultModel(el, value, modifiers)
 } else if(! config.isReservedTag(tag)) { genComponentModel(el, value, modifiers)// component v-model doesn't need extra runtime
   return false
 } else if(process.env.NODE_ENV ! = ='production') {
   // Other tags' V-models are not supported, throw a warning
 }

 // Returning true indicates that runtime assistance from the framework is required
 // ensure runtime directive metadata
 return true
}
Copy the code

2.2.1 genDefaultModel

Methods location: SRC/platforms/web/compiler directives/model. The js – > function genDefaultModel

Method parameters:

  1. el.astThe node object
  2. value:v-modelThe value of the binding
  3. modifiers: Directive modifier

The v-model instruction that handles the default type=text/textarea of the input tag needs a runtime helper: This part is known as the binding of input event to input. When the event is triggered, the value pointed by the V-model is updated. Then the data observation of Vue is triggered, and the patching page is updated.

function genDefaultModel (el: ASTElement, value: string, modifiers: ? ASTModifiers): ?boolean {
  // Get the type of the input binding
  const type = el.attrsMap.type
  
  if(process.env.NODE_ENV ! = ='production') {
    // The v-model and value attributes cannot be present at the same time
  }

  // Get the modifiers lazy, number, trim
  const { lazy, number, trim } = modifiers || {}
  constneedCompositionGuard = ! lazy && type ! = ='range'
  
  // Determine the type of event to bind to the input based on type and lazy
  const event = lazy
    ? 'change'
    : type === 'range'
      ? RANGE_TOKEN
      : 'input'

  // The value expression in event handler
  let valueExpression = '$event.target.value'
  if (trim) {
    // If the trim modifier exists, trim the new value in the input box
    valueExpression = `$event.target.value.trim()`
  }
  if (number) {
    // If the number modifier exists, it is converted to a number. _n is also a render helper function
    valueExpression = `_n(${valueExpression}) `
  }
  
  // Generate event handler code
  let code = genAssignmentCode(value, valueExpression)
  if (needCompositionGuard) {
    code = `if($event.target.composing)return;${code}`
  }

  // Input can only recognize the value attribute, which is used to display the value of the input field
  addProp(el, 'value'.` (${value}) `)
  
  // This step is the familiar process of binding events to input
  addHandler(el, event, code, null.true)
  if (trim || number) {
     // Bind the blur event if the trim or number modifier is present, and re-render the input box if it loses focus
     // Rerender to trim or numeric value
    addHandler(el, 'blur'.'$forceUpdate()')}}Copy the code

2.2.2 genAssignmentCode

Methods location: SRC/compiler directives/model. The js – > function genAssignmentCode

Method parameters:

  1. value.inputThe value of the element binding
  2. assignment: want to assigninputThe new value

Method function: generate v-model instruction value assignment statement help function;

export function genAssignmentCode (value: string, assignment: string) :string {
  const res = parseModel(value) // Parse the value as an expression, e.g. V-model ="obj.value"
  if (res.key === null) {
    // If key is null, v-model binding is not obj.value
    return `${value}=${assignment}`
  } else {
    // value is bound to the value expression obj.val, which needs to be updated by calling $set()
    return `$set(${res.exp}.${res.key}.${assignment}) `}}Copy the code

2.3 V-HTML and HTML modules

Methods location: SRC/platforms/web/compiler directives/HTML js – > function HTML

Method parameters:

  1. el.astThe node object
  2. dir:v-htmlThe value of the binding

The el.innerHTML () method changes the value of the V-HTML binding to the el.innerhtml property. The value is the return value of the _s method, which is also a helper of the render function.

Note that the V-HTML directive does not require a runtime helper, so it does not return a value, or undefined;

export default function html (el: ASTElement, dir: ASTDirective) {
  if (dir.value) {
    addProp(el, 'innerHTML'.`_s(${dir.value}) `, dir)
  }
}
Copy the code

2.4 V-text and Text modules

Methods location: SRC/platforms/web/compiler directives/text. Js – > the function of the text

Method parameters:

  1. el.astThe node object
  2. dir:v-textThe value of the instruction binding

The el.textContext () method changes the value of the V-text binding to the el.textContext property, which is the return value of the _s(the instruction binding value) method. _s is the render helper function.

Note that the V-text instruction does not require a runtime helper, so it does not return a value, or undefined;

export default function text (el: ASTElement, dir: ASTDirective) {
  if (dir.value) {
    addProp(el, 'textContent'.`_s(${dir.value}) `, dir)
  }
}
Copy the code

Third, summary

This tutorial discusses in detail the genDirectives method, which is responsible for calling the corresponding directives to handle the directives in EL. Directives and returns whether the corresponding directives require a run-time auxiliary identifier.

Directives are from baseOptions.directives(in fact, other directives can be passed in when creating the compiler to compile the directives), baseOptions.directives = {text, HTML, model};

  • textMethods to deal withv-textThat will bev-textThe bound value is processed and saved toel.textContentProperties;
  • htmlMethods to deal withv-htmlThat will bev-htmlThe bound value is processed and saved toel.innerHTMLProperties;
  • modelMethods to deal withv-modelThis method is based onEl.com ponent, el.tag, inputtypeValue to handle various types ofv-modelThe instructions.v-modelIs the responsibility of the runtime, and run-time assistance is for usev-modelIs bound to different eventshandler.handlerThe core is renewalv-modelBinding values are processed in this proceduretrim/number/lazyEtc modifier;