The author is a contributor of VUE-Loader (#16)

preface

Vue-loader source code parsing series, please refer to the outline of vue-loader source code parsing series before reading this article

What does selector do

const path = require('path')
const parse = require('./parser')
const loaderUtils = require('loader-utils')

module.exports = function (content) {
  / / a little
  const query = loaderUtils.getOptions(this) | | {}/ / a little
  const parts = parse(content, filename, this.sourceMap, sourceRoot, query.bustCache)
  let part = parts[query.type]
  / / a little
  this.callback(null, part.content, part.map)
}
Copy the code

As you can see, the selector code is pretty simple, parses.vue from the Parser to parts, which has style, script, template. The corresponding part can be returned based on different queries. It’s clear that the Parser does the job of parsing.vue, so let’s dig deeper into parser

What does parser do


const compiler = require('vue-template-compiler')
const cache = require('lru-cache') (100)

module.exports = (content, filename, needMap, sourceRoot, bustCache) = > {
  const cacheKey = hash(filename + content)
  / / a little
  let output = cache.get(cacheKey)
  if (output) return output
  output = compiler.parseComponent(content, { pad: 'line' })
  if (needMap) {
    // The sourceMap code is omitted
  }
  cache.set(cacheKey, output)
  return output
}

Copy the code

Similarly, I have removed some of the code to make it easier for readers to understand the main process.

As can be seen from the above code, the work of parsing.vue is actually handed over to compiler.parsecomponent, so we need to go further into compiler. Note that vue-template-compiler is not part of vue-loader, as can be seen from the NPM home page of vue-template-Compiler. Vue-template-compiler was originally part of the VUE ontology, not a separate package. ParseComponent’s logic is in vue/ SRC/SFC /parser.js.

The source code is as follows

What does parseComponent do

/** * Parse a single-file component (*.vue) file into an SFC Descriptor Object. */
export function parseComponent (content: string, options? : Object = {}) :SFCDescriptor {
  const sfc: SFCDescriptor = {
    template: null.script: null.styles: [].customBlocks: []}let depth = 0
  letcurrentBlock: ? (SFCBlock | SFCCustomBlock) =null

  function start (tag: string, attrs: Array
       
        , unary: boolean, start: number, end: number
       ) {
    / / a little
  }

  function checkAttrs (block: SFCBlock, attrs: Array<Attribute>) {
    / / a little
  }

  function end (tag: string, start: number, end: number) {
    / / a little
  }

  function padContent (block: SFCBlock | SFCCustomBlock, pad: true | "line" | "space") {
    / / a little
  }

  parseHTML(content, {
    start,
    end
  })

  return sfc
}

Copy the code

ParseComponent has the following variables

  • Process object SFC

    After extracting the CSS, javaScript, and HTML from.vue, store it in this object

  • Variable the depth

    The depth of the node currently being processed, for example, for
    , the current depth is 3 for foo and 2 for .

  • currentBlock

    The node being processed, and the attR and content of the node.

  • The start () function

    Process openTag when an openTag node is encountered. Logic is not very complex, readers can directly look at the source code. It is worth noting that style is stored as an array

  • The end () function

    Processing of closeTag when the closeTag node is encountered.

  • Function checkAttrs

    Attrs for the current node

  • Function parseHTML

    This is done with an external function that passes in the content (which is essentially the content of.vue) and objects made up of the start and end functions. It seems that parseHTML is the key to decomposing.vue

    As before, we need to dig into the parseHTML function to see what it does to.vue

What does parseHTML do


export function parseHTML (html, options) {
  const stack = []
  const expectHTML = options.expectHTML
  const isUnaryTag = options.isUnaryTag || no
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  let index = 0
  let last, lastTag
  while (html) {
    last = html
    if(! lastTag || ! isPlainTextElement(lastTag)) {// Here we separate the template
    } else {
      // Separate style/script
  }

  / / a little

  // advance n characters
  function advance (n) {
    / / a little
  }

  // Parse openTag like 
      
  function parseStartTag () {
    / / a little
  }

  / / openTag processing
  function handleStartTag (match) {
    / / a little
    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }

  / / closeTag processing
  function parseEndTag (tagName, start, end) {
    / / a little
    if (options.start) {
      options.start(tagName, [], false, start, end)
    }
    if (options.end) {
      options.end(tagName, start, end)
    }
  }
}

Copy the code

Going further, I want to remind the reader that the purpose of a selector is to separate templates, javaScript, and CSS from.vue. With this purpose in mind, let’s look at parseHTML again.

The entire parseHTML function consists of:

  • A while loop

    In the while loop, there are two large branches, one for template and one for script and style.

  • Function in advance

    Skip the text forward

  • Function parseStartTag

    Check whether the current node is openTag

  • Function handleStartTag

    To handle openTag, you use the start() function mentioned earlier

  • Function parseEndTag

    Check whether the current node is closeTag and use the end() function

By combining these functions, the WHILE loop splits the SFC into three different parts, allowing the reader to interpret the source logic by comparing my comments with the source code.

By the way, there’s something wrong with parseHTML, which should be called parseSFC.

  • Vue-loader source code analysis one of the overall analysis
  • Style -compiler (in writing)
  • Template-compiler (in writing)

The author blog

The author making

The author weibo