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

Previously on & Background

The $mount method is registered and the template compileToFunctions are available.

CompieToFunctions is a nesting function that returns the createCompiler method, which in turn returns the createCompilerCreator function, CreateCompileToFunction as an attribute of the createCompiler return value is createCompileToFunction(compile).

You can see what happens when you create the template compiler functions, compileToFunctions.

This process can be called the whole Vue source code in the most complex part, if a mess, insist on coming again. To say the truth, I also read two times before I began to write this small composition, in the process of writing also need to comb this process, both review is to verify their understanding is correct.

Second, the createCompileToFunctions

Methods location: SRC/compiler/to – function. Js – > createCompileToFunctionFn

Method arguments: compile method, which is declared in the createCompiler method and passed in when createCompileToFunctions is called

Methods:

Create compileToFunctions and cache them, the same methods you used to compile templates in the $mount method earlier.

export function createCompileToFunctionFn (compile: Function) :Function {
  const cache = Object.create(null)
  
  return function compileToFunctions (
    template: string, $mount = $mountoptions? : CompilerOptions, vm? : Component) :CompiledFunctionResult {}}Copy the code

Third, compileToFunctions

CreateCompileToFunctionFn return values

Methods location: SRC/compiler/to – function. Js – > createCompileToFunctionFn – > return function compileToFunctions

Method parameters:

  1. template: String of templates to compile
  2. optionsPassed:compileMethod compile options; This parameter is in$mountMethod called in;
// $mount snippet
 compileToFunctions(template, {
  outputSourceRange: process.env.NODE_ENV ! = ='production'.// In a non-production environment, the index of the starting and ending positions of the HTML tag attributes in the template string is recorded when compiling the AST
  shouldDecodeNewlines,
  shouldDecodeNewlinesForHref,
  delimiters: options.delimiters, // Qualifier: {{}} with the default mustache syntax
  comments: options.comments // Whether to keep comments
})
Copy the code
  1. vm:VueThe instance

Methods:

  1. To deal withoptions.extendTo an empty object is a copy operation;
  2. Development environment monitoringCSPRestrictions,CSPIs content security policy;
  3. Check the cache and use it if this compilation hits the cache.
  4. performcreateCompileToFunctionFncreateCompilerMethod passed incompileFunctions;
  5. Handle compilation errors and output;
  6. I’m going to compile itrenderstaticRenderFnsthroughnew Function()Becomes a function;
  7. Caching compilation results;
export function createCompileToFunctionFn (compile: Function) :Function {
  const cache = Object.create(null)
  
  return function compileToFunctions (template: string, options? : CompilerOptions, vm? : Component) :CompiledFunctionResult {
    Options is passed to compileToFunctions when you call them
    options = extend({}, options) 
    const warn = options.warn || baseWarn
    delete options.warn

    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production') {
      // Check CSP limits, CSP can affect the compiler's work, check ahead of time
      // detect possible CSP restriction
      try {
        new Function('return 1')}catch (e) {
        if (e.toString().match(/unsafe-eval|CSP/) {}}}// If the cache is hit, the result of the last compilation is directly fetched from the cache
    // key is delimiters + template, + is string concatenation
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
      return cache[key]
    }

    Compiled executes the compilation method and assigns the result to Compiled
    / / this is the compile createCompilerCreator call createCompileToFunctionsFn method when the incoming callback, in SRC/compiler/create - compiler. Js
    const compiled = compile(template, options) 
    
    // Check error and tip generated during compilation and print them to console, respectively
    if(process.env.NODE_ENV ! = ='production') {}// Use new Function(code) to convert the compiled string code into a Function
    const res = {}
    const fnGenErrors = []
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code= > {
      return createFunction(code, fnGenErrors)
    })

   
    // Handle errors in the code conversion process above
    if(process.env.NODE_ENV ! = ='production') {}// Cache the compiled results
    return (cache[key] = res)
  }
}
Copy the code

3.1 Compile in createCompiler

Before we say compile, take a look at the createCompiler code to see where compile is declared

export function createCompilerCreator (baseCompile: Function) :Function {
  return function createCompiler (baseOptions: CompilerOptions) {
   
    // This is the compile you've been waiting for
    function compile (template, options) {}

    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
Copy the code

Compile is the createCompileToFunctions method passed in when the createCompiler method is called. Compile itself is also the createCompiler declaration.

Method parameters:

  1. template: template string
  2. options: creates parameters required by the compiler, specifying such asdelimiters

Method function: merge baseOptions, call baseCompile compile template, return compile result;

export function createCompilerCreator (baseCompile: Function) :Function {
  return function createCompiler (baseOptions: CompilerOptions) {
  
    function compile (template: string, options? : CompilerOptions) :CompiledResult {
      // Create an object based on baseOptions, which inherits baseOptions
      const finalOptions = Object.create(baseOptions)
      const errors = []
      const tips = []

      // Log, which records error and tip
      let warn = (msg, range, tip) = > {
        (tip ? tips : errors).push(msg)
      }

      // Merge options with baseOptions
      if (options) {
        if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
         
          const leadingSpaceLength = template.match(/^\s*/) [0].length
          
          // Throw a strong warning method
          warn = (msg, range, tip) = >{}}// merge from module
        if (options.modules) {
          finalOptions.modules =
            (baseOptions.modules || []).concat(options.modules)
        }

        // merge instructions
        if (options.directives) {
          finalOptions.directives = extend(
            Object.create(baseOptions.directives || null),
            options.directives
          )
        }

        // Copy the other options to finalOptions
        for (const key in options) {
          if(key ! = ='modules'&& key ! = ='directives') {
            finalOptions[key] = options[key]
          }
        }
      }

      finalOptions.warn = warn

      / / call baseCompile
      const compiled = baseCompile(template.trim(), finalOptions)
      if(process.env.NODE_ENV ! = ='production') {
        detectErrors(compiled.ast, warn)
      }

      // Mount errors and prompts generated during compilation to the compilation result
      compiled.errors = errors
      compiled.tips = tips
      return compiled
    }

    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
Copy the code

Here, we are temporarily no matter the options. The modules/directives, in our demo, first rendering options of modules and directives are undefined.

And then baseCompile is called, so where does baseCompile come from?

3.2 createCompileCreator baseCompile received

Method location: baseCompile is passed as a parameter to the createCompilerCreator method:

export const createCompiler = createCompilerCreator(function baseCompile (template: string, options: CompilerOptions) :CompiledResult {
  // This function is baseCompile
})
Copy the code

CreateCompiler createCompiler returns {compile, compileToFunctions}; createCompiler returns {compile, compileToFunctions}; createCompiler returns {compile, compileToFunctions}.

Method parameters:

  1. template, the template string to compile
  2. Configuration items required during compilation

Here’s what baseCompile does:

  1. Call the parse method to compile the compiled templateASTNode. eachASTA node contains all the information about a template tag, such as tag name, attributes, slots, parent nodes, and child nodes
  2. whenoptions.optimizeDon’t forfalseThe callparseMethods Static nodes were optimized.
  3. callgenerateMethod will beASTbecomerender/staticRenderFnsThis is the string form code forrender/staticRenderFnsFunction’s predecessor;
export const createCompiler = createCompilerCreator(function baseCompile (template: string, options: CompilerOptions) :CompiledResult {
  // The template is parsed into an AST, where all the information on the node is set
  // For example, tag information, attribute information, slot information, parent node, child node, etc
  
  const ast = parse(template.trim(), options)
  // Walk through the AST to mark each node as a static node to further mark the static root node
  // The static nodes are not updated in subsequent updates, which is an important way to improve performance
  if(options.optimize ! = =false) {
    optimize(ast, options)
  }

  // Generate code to convert the AST into a string of executable render functions:

  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
Copy the code

3.2.1 ast object

The AST object is an abstract description of our template, with one object describing all the information on the node, such as the tag, the tag’s inline attributes, the child node, and the parent node. For example, our template file in test.html looks like this:

<div id="app">
 {{ msg }}
 <some-com :some-key="forProp"></some-com>
 <div>someComputed = {{someComputed}}</div>
 <div class="static-div">Static node</div>
</div>
Copy the code

The compiled AST looks like this:

3.2.2 Converting ast to Code

The code derived from baseCompile parse and generate is a string of js function bodies:

To make it a little more intuitive, here’s a manually formatted code.render

  code.render = with (this) {
    return _c( // <div id=app>
      'div',
      { attrs: { 'id': 'app'}},// div attribute id = app
      [ // this array is a child of div#app
        _v('\n\t' + _s(msg) + '\n\t'), // The first child
        _c( // second child 
      
          'some-com',
          { attrs: { 'some-key': forProp } } // some-com attributes
         ),
        _v(' '), // The third child
        _c( // The fourth child
          'div',
          [_v('someComputed = ' + _s(someComputed))]
        ),
        _v(' '), / / the fifth
        _c( / / the sixth
          'div',
          { staticClass: 'static-div' },
          [_v('Static node')])],1)}})Copy the code

Referring to the template in HTML above, you can see the body of the code that goes to the AST and then turns the AST into a render function. Both the AST and the Render function describe the HTML template.

Four,

Generate ast (test.html) and generate ast (code. Render). Note that the body of the Function is not the render Function because it is still a blob of string, after the new Function is the Function;

A lot of people write Vue source code did not write this doll part, I tried to write this part is to understand the benefits of such a design, but obviously not realize, this pit first do not fill, wait for me to see, understand in fill it; So if you think this part of the dolls is complicated, just look at what compile and baseCompile do first.

Of course, if you find this wonderful, please feel free to let me know in the comments section, thanks a million ~