Vue – loader address

Vue-loader is a webpack loader that allows you to write vUE Components in single-file Components format. Its function is to extract template, script, style and so on from *. Vue file, and then form a JS file that can be run in the browser through vue-template-compiler, style-loader and other plug-ins.

Vue-loader processing SFC (Single file component)

How does vue-loader handle SFC? He parses the SFC source into the SFC descriptor, the source address, using @vue/ component-Compiler-utils (described below)

// vue-loader lib/index.js
const { parse } = require('@vue/component-compiler-utils');

module.exports = function (source) {
  // Parse the source code to get the descriptor
  const descriptor = parse({ source, ... });
	
  // Print the result
  console.log(descriptor);
  /** descriptor { template: { ... }, script: { ... }, styles: [ ... ] , customBlocks: [], errors: [] } */
}
Copy the code

It then processes the source code address for each language block according to the SFC descriptor

  // If the template block exists
  if (descriptor.template) { ... }
  // If the script block exists
  if (descriptor.script) { ... }
  // If a style block exists (multiple style blocks are supported)
  if (descriptor.styles.length) { ... }
  Vue also supports custom blocks
  if (descriptor.customBlocks && descriptor.customBlocks.length) { ... }
Copy the code

Results printed after processing:

// The code returned from the main loader source.vue

// import the <template> block
import render from 'source.vue? vue&type=template'

// import the <script> block
import script from 'source.vue? vue&type=script'
export * from 'source.vue? vue&type=script'

// import <style> blocks
import 'source.vue? vue&type=style&index=1'

script.render = render
export default script
Copy the code

This is the result of using vue-loader to process SFC. What does vue&type=template mean? What does it do? This starts with an understanding of pitch Loader and the VueLoaderPlugin

Pitch Loader

  • Normal Loader: A loader is essentially a JavaScript module that exports functions. The functions that are exported by this module (or the default exported functions in the case of an ES6 module) are called Normal Loader.Note that Normal loaders are different from loaders defined in the Webpack Loader category. In Webpack, loaders can be classified into four categories: Pre pre, POST post, normal normal, and inline inline. Pre and post loader can passruleThe object’senforceProperty to specify.
  • Pitch Loader: When developing a loader, we can add one on top of the exported functionpitchProperty, whose value is also a function. The function is calledPitching Loader.

For example, we define three loaders: aLoader, bLoader, and cLoader

// aLoader
function aLoader(content, map, meta) {
  console.log("Execute aLoader Normal Loader");
  content += "aLoader]";
  return `module.exports = '${content}'`;
}

aLoader.pitch = function (remainingRequest, precedingRequest, data) {
  console.log("Start aLoader Pitching Loader");
};

// bLoader
function bLoader(content, map, meta) {
  console.log("Start bLoader Normal Loader");
  return content + "bLoader->";
}

bLoader.pitch = function (remainingRequest, precedingRequest, data) {
  console.log("Start bLoader Pitching Loader");
};

// cLoader
function cLoader(content, map, meta) {
  console.log(Start executing cLoader Normal Loader);
  return content + "[cLoader->";
}

cLoader.pitch = function (remainingRequest, precedingRequest, data) {
  console.log("Start executing cLoader Pitching Loader");
};

// config
module: {
  rules: [{test: /.txt$/i,
      use: ["a-loader"."b-loader"."c-loader"],},],},Copy the code

[” A-loader “, “b-loader”, “c-loader”] is executed in c-> B -> A, but this is only the Normal loader execution sequence. Let’s look at the Pitching and Normal Loader execution sequence from the print results:

Start executing aLoader Pitching Loader... Start executing bLoader Pitching Loader... Start executing cLoader Pitching Loader... Start cLoader Normal Loader Start bLoader Normal Loader Start aLoader Normal LoaderCopy the code

It was obvious for our example that the Pitching Loader was executed from left to right and the Normal Loader was executed from right to left. The Pitching Loader also uses a circuit breaker when a pitch Loader returns a non-undefined value. For example, if you return a value in bloader.pitch, the result will look like this:

VueLoaderPlugin

We know that the purpose of the Webpack plug-in is to hook into all the critical events that are triggered during each compilation, and that the Webpack configuration can be retrieved and modified using the Compiler.options method in the plug-in instance.

In the processing process of VueLoaderPlugin, module. Rules have been modified, and new rules of pitcher and cloneRules have been added on the basis of the original rules. Recognize queryStrings of the form? Vue&type =template and match blocks of code from different languages to the corresponding rule.

class VueLoaderPlugin {
  apply (compiler) {
    // Modify the Webpack configuration
    const rawRules = compiler.options.module.rules;
    const { rules } = newRuleSet(rawRules); .// pitcher
    const pitcher = {
      loader: require.resolve('./loaders/pitcher'),
      resourceQuery: query= > {
        if(! query) {return false }
        const parsed = qs.parse(query.slice(1))
        // Match whether the first query condition is vue
        returnparsed.vue ! =null
      },
      options: {... }}// Replace the original module. Rules with pitcher and clonedRulescompiler.options.module.rules = [ pitcher, ...clonedRules, ...rules ]; }}Copy the code

Here we inject the pitcher rule, above source.vue? If vue&type=script is matched by the pitcher rule, it will be processed by pitch function, and the result is returned in pitch, because the circuit breaker mechanism will skip the rest of the loader. This function generates the inline lodaer based on the type argument:

export * from "-! . /.. /node_modules/vue-loader/lib/loaders/templateLoader.js?? vue-loader-options! . /.. /node_modules/vue-loader/lib/index.js?? vue-loader-options! ./index.vue? vue&type=template&id=21fec300&"
Copy the code

The in-line loaders are executed from right to left. After getting the above request, Webpack uses vue-loader first, then template-loader to process it, and finally the render function is produced.

In the transformation result of PitchLoader, vue-loader will be used as the first loader to process. Different from the first vue-loader, this vue-loader only extracts the source code of the syntax block in SFC. And submit to the subsequent loader for processing.

@vue/component-compiler-utils

The @vue/component-compiler-utils function is to parse the Vue SFC and get the HTML (template) and JavaScript(script block) source code, respectively. For HTML source code, we can use vue-template-compiler module again to parse it into the corresponding render function of the template.

parse(ParseOptions): SFCDescriptor

The SFC is resolved to an SFC descriptor with a Source map. The vue-template-compiler is passed in through the compiler.

interface ParseOptions {
  source: stringfilename? :string
  compiler: VueTemplateCompiler
  // https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#compilerparsecomponentfile-options
  // default: { pad: 'line' }compilerParseOptions? : VueTemplateCompilerParseOptions sourceRoot? :stringneedMap? :boolean
}

interface SFCDescriptor {
  template: SFCBlock | null
  script: SFCBlock | null
  styles: SFCBlock[]
  customBlocks: SFCCustomBlock[]
}

interface SFCCustomBlock {
  type: string
  content: string
  attrs: { [key: string] :string | true }
  start: number
  end: numbermap? : RawSourceMap }interface SFCBlock extendsSFCCustomBlock { lang? :stringsrc? :stringscoped? :boolean
  module? :string | boolean
}
Copy the code

compileTemplate(TemplateCompileOptions): TemplateCompileResults

Compile the template into JavaScript code. The vue-template-compiler is passed in via the Compiler option.

It can also optionally preprocess any template engine using consolidate.

interface TemplateCompileOptions {
  source: string
  filename: string

  compiler: VueTemplateCompiler
  https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#compilercompiletemplate-options
  // default: {}compilerOptions? : VueTemplateCompilerOptions// Template preprocessorpreprocessLang? :stringpreprocessOptions? :any

  // Convert the resource URL found in the template to the 'require()' call
  // This option is off by default. If set to true, the default is:
  / / {
  // video: ['src', 'poster'],
  // source: 'src',
  // img: 'src',
  // image: 'xlink:href'
  // use: 'xlink:href'
  // }transformAssetUrls? : AssetURLOptions |boolean

  // Is the option specified by vue-template-es2015-compiler, i.e. a Buble forktranspileOptions? :anyisProduction? :boolean  // default: falseisFunctional? :boolean  // default: falseoptimizeSSR? :boolean   // default: false

  // Whether to beautify the compiled render function (only available in the development environment)
  // Default value: trueprettify? :boolean
}

interface TemplateCompileResult {
  code: string
  source: string
  tips: string[]
  errors: string[]}interface AssetURLOptions {
  [name: string] :string | string[]}Copy the code

The resulting JavaScript code looks like this;

var render = function (h) { / *... * /}
var staticRenderFns = [function (h) { / *... * /}, function (h) { / *... * /}]
Copy the code

It does not assume any modular system. You are responsible for handling exports when necessary.

compileStyle(StyleCompileOptions)

The original input CSS is scoped CSS transformed. It doesn’t deal with preprocessors. This step is skipped if the component does not use scoped CSS.

interface StyleCompileOptions {
  source: string
  filename: string
  id: stringmap? :anyscoped? :booleantrim? :booleanpreprocessLang? :stringpreprocessOptions? :anypostcssOptions? :anypostcssPlugins? :any[]}interface StyleCompileResults {
  code: string
  map: any | void
  rawResult: LazyResult | void // Lazy processing of the original result from PostCSS
  errors: string[]}Copy the code

compileStyleAsync(StyleCompileOptions)

Same as compileStyle(StyleCompileOptions), but return a Promise object that resolves the StyleCompileResults. PostCSS plug-in for asynchrony.

vue-template-compiler

Template => ast => render template => ast => render

The template

<div>
  <h1 @click="handler">title</h1>
  <p>some content</p>
</div>
Copy the code

Rendering function

render (h) {
  return h('div', [
    h('h1', { on: { click: this.handler } }, 'title'),
    h('p'.'some content')])}Copy the code

The h function in the render function internally calls vm.$createElement to generate the virtual DOM

The role of template compilation

  • Vue 2.x uses VNode to describe views and various operations, but it is not possible for users to write vNodes or render functions themselves. Users only need to write HTML-like code and use the compiler to convert the template into the render function that returns VNode

  • Vue files are converted to render functions by the WebPack build process, webpack itself does not support conversion, it is supported by vue-loader

  • Runtime compilation: Runtime compilation requires the full version of VUE (including the compiler), which compiles the template into the render function while the project is running, but the disadvantage is that the VUE is bulky.

  • Build-time compilation: Vue-CLI comes with a run-time version of VUE, which is compiled using Webpack + vue-Loader, which is smaller and faster at run time with no extra operation time.

Experience the results of template compilation

In vue. js with the compiler version, use template or EL to set the template

<div id="app">
  <h1>Vue<span>Template compilation process</span></h1>
  <p>{{ msg }}</p>
  <comp @myclick="handler"></comp>
</div>
<script src=".. /.. /dist/vue.js"></script>
<script>
  Vue.component('comp', {
    template: '<div>I am a comp</div>'
  })
  const vm = new Vue({
    el: '#app'.data: {
      msg: 'Hello compiler'
    },
    methods: {
      handler () {
        console.log('test')}}})console.log(vm.$options.render)
</script>
Copy the code

The result of render output after compiling

(function anonymous() {
  with (this) {
    return _c(
      "div",
      { attrs: { id: "app" } },
      [
        _m(0),
        _v(""),
        _c("p", [_v(_s(msg))]),
        _v(""),
        _c("comp", { on: { myclick: handler } }),
      ],
      1); }});Copy the code

_c: createElement method

_m: renderStatic, handling static nodes

_v: createTextVNode, handles text nodes

_s: Handles strings

Vue Template Explorer

  • Vue-template-explorer: vue 2.6 tool to compile templates into render functions
  • Vue-next-template-explorer: Vue 3.0 Beta compiles templates into render functions

Template compilation process

Parsing, optimizing, generating

Compile the entrance

src\platforms\web\entry-runtime-with-compiler.js

Vue.prototype.$mount = function (.// Convert template to render function
const{ render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV ! = ='production',
    shouldDecodeNewlines,
    shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
  }, this)
  options.render = render
  options.staticRenderFns = staticRenderFns
  ......
)
Copy the code

Debug compileToFunctions() to generate render functions:

  • compileToFunctions: src\compiler\to-function.js
  • complie(template, options): SRC \compiler\create-compiler.js, which merges options
  • baseCompile(template.trim(), finalOptions): SRC \compiler\index.js, the core compiler function
export const createCompiler = createCompilerCreator(function baseCompile (template: string, options: CompilerOptions) :CompiledResult {
  // Convert the template to an AST abstract syntax tree
  const ast = parse(template.trim(), options)
  if(options.optimize ! = =false) {
    // Optimize the abstract syntax tree
    optimize(ast, options)
  }
  // Generate the abstract syntax tree as a string of JS code
  const code = generate(ast, options)
  return {
    ast,
    // Render function
    render: code.render,
    // Static rendering function to generate a static VNode tree
    staticRenderFns: code.staticRenderFns
  }
})
Copy the code

Resolution – the parse

The parser parses the template into an abstract language tree AST, and only after the template is parsed into an AST can it be used to optimize or generate code strings.

const ast = parse(template.trim(), options)

//src\compiler\parser\index.js
parse()
Copy the code

Optimization – optimize

  • Optimize the abstract syntax tree to detect whether the child nodes are pure static nodes
  • Once a purely static node is detected, such as a node that never changes
    • Upgrade to constant. Nodes are not recreated during re-rendering
    • The static subtree is directly skipped during patch

Generate to generate

// src\compiler\index.js
const code = generate(ast, options)

// src\compiler\codegen\index.js
export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
) :CodegenResult {
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns // Render function corresponding to static root node}}// Convert strings to functions
// src\compiler\to-function.js
function createFunction (code, errors) {
  try {
    return new Function(code)
  } catch (err) {
    errors.push({ err, code })
    return noop
} }
Copy the code

Refer to the link

  • vue-loader

  • Figure out how to understand Webpack Loader at once

  • How VueLoader:.vue files are packaged?

  • What does vue-loader do

  • Deeper into vue-loader principle