preface

Vue-loader is a webpack loader that can be used to parse.vue files. The main function is to parse a single file component (SFC) into a component module that the VUE Runtime recognizes. Its use is as follows

// webpack.config.js
module.export = {
  / /...
  module: {
    rules: [{test: /\.vue$/,
        use: ['vue-loader']},]},plugins: [
    new VueLoaderPlugin(),
  ]
  / /...
}
Copy the code

There are two main components: the configuration of module.rules and the instantiation of the VueLoaderPlugin in the plugins. Before exploring what each of these parts does, let’s take a look at what js modules a.vue file is packaged into via WebPack after such a configuration.

{
/ * * * / "./test.vue":
/ * * * / (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var MODULE_0__ = __webpack_require__("./test.vue? vue&type=template&id=13429420&scoped=true&");
var MODULE_1__ = __webpack_require__("./test.vue? vue&type=script&lang=js&");
var MODULE_2__ = __webpack_require__("./test.vue? vue&type=style&index=0&id=13429420&scoped=true&lang=scss&");
var MODULE_3__ = __webpack_require__("./lib/vue-loader/runtime/componentNormalizer.js");
/* normalize component */
var component = Object(MODULE_3__["default"])(
  MODULE_1__["default"],
  MODULE_0__["render"],
  MODULE_0__["staticRenderFns"].false.null."13429420".null  
)
/* hot reload */
if (false) { var api; }
component.options.__file = "test.vue"
__webpack_exports__["default"] = (component.exports);
/ * * * /}}),Copy the code

MODULE_0__, MODULE_1__, MODULE_2__, MODULE_3__, MODULE_0__, MODULE_1__, MODULE_0__, MODULE_1__ MODULE_2__ corresponds exactly to the
, , and

modules in our.vue file. Then the MODULE_3__ module combines MODULE_0__ and MODULE_1__ into our standard VUE Component. By analyzing the source files and the target artifacts, we have an outline of what vue-Loader does. The detailed process will be analyzed in depth below.

The principle of analysis

The.vue file conversion is divided into three phases.

  1. Stage 1: Passvue-loader.vueThe document is converted into an intermediate, which goes something like this,
import { render, staticRenderFns } from "./test.vue? vue&type=template&id=13429420&scoped=true&"
import script from "./test.vue? vue&type=script&lang=js&"
export * from "./test.vue? vue&type=script&lang=js&"
import style0 from "./test.vue? vue&type=style&index=0&id=13429420&scoped=true&lang=scss&"
import normalizer from ! "" ./lib/vue-loader/runtime/componentNormalizer.js"
var component = normalizer(
  script,
  render,
  staticRenderFns,
  false.null."13429420".null 
)
component.options.__file = "test.vue"
export default component.exports
Copy the code
  1. Stage two: Passpitcher-loader(theloaderIs through thevueloaderpluginInjected into thewebpackConvert the first stage intermediate into another stage product.

Import {render, staticRenderFns} from “./test.vue? Vue&type =template&id=13429420&scoped=true&” ./lib/vue-loader/loaders/templateLoader.js?? vue-loader-options! ./lib/vue-loader/index.js?? vue-loader-options! ./test.vue? vue&type=template&id=13429420&scoped=true&

  1. The third stage: the second stage transformationrequestRequest, through the correspondingloaderTo process, for example:-! ./lib/vue-loader/loaders/templateLoader.js?? vue-loader-options! ./lib/vue-loader/index.js?? vue-loader-options! ./test.vue? vue&type=template&id=13429420&scoped=true&, can use firstvue-loaderProcess and then usetemplateLoaderI’m going to deal with it, and I’m going to get itMODULE_0__Of PI. Something like this:
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "render".function() { return render; });
__webpack_require__.d(__webpack_exports__, "staticRenderFns".function() { return staticRenderFns; });
var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c("div", [
    _c("span", { staticClass: "haha" }, [
      _vm._v("\n " + _vm._s(_vm.msg) + "\n ")]])}var staticRenderFns = []
render._withStripped = true
}),
Copy the code

: : : For those who are familiar with vUE source code, TIP must be familiar with the above products. The generated render function is the result of parsing the template template, and the execution result of render function is its corresponding vNode, which is the entry parameter of vue patch stage. Each stage is described in detail below.

The first stage

As the graph shows,

The.vue file can only be hit by Vue-Loader out of all the rules configured so far. Let’s take a look at what Vue-Loader does. (/ lib/index. Js) [github.com/vuejs/vue-l…

module.exports = function (source) {
  // source is the source file read from test.vue
  const loaderContext = this
  // Convert the test.vue file into a file descriptor using the @vue/component-compiler-utils parse parser
  // The compiler parameter is the Vue-template-compiler template parser
  const descriptor = parse({
    source,
    compiler: options.compiler || loadTemplateCompiler(loaderContext),
    filename,
    sourceRoot,
    needMap: sourceMap
  })
  // template
  let templateRequest
  if (descriptor.template) {
    templateImport = `import { render, staticRenderFns } from ${request}`
    // 'import { render, staticRenderFns } from "./test.vue? vue&type=template&id=13429420&scoped=true&"'
  }
  let scriptImport = `var script = {}`
  if (descriptor.script) {
    scriptImport = // ...
  }
  let stylesCode = ` `
  if (descriptor.styles.length) {
    stylesCode = / /...
  }
  let code = `
${templateImport}
${scriptImport}
${stylesCode}
/* normalize component */
import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
var component = normalizer(
  script,
  render,
  staticRenderFns,
)`.trim() + `\n`
  code += `\nexport default component.exports`
  return code
}
Copy the code

With the above comment, Vue-Lodaer will now read the source file, and the parse parser in @vue/component-compiler-utils will get the descriptor for the source file. Each block is processed and the corresponding module request is generated. The Normalizer function splices each block together to form a VUE component. There are a lot of details in there that I won’t go into here.

The second stage

As the graph shows,

The pitcher-loader, which is injected into the Webpack via the Vueloaderplugin, converts the first stage intermediate into the next stage. Import {render, staticRenderFns} from “./test.vue? Vue&type =template&id=13429420&scoped=true&” ./lib/vue-loader/loaders/templateLoader.js?? vue-loader-options! ./lib/vue-loader/index.js?? vue-loader-options! ./test.vue? Vue&type =template&id=13429420&scoped=true&

  1. pitcher-loaderHow is it injected intoloadersIn the?
  2. ./test.vue? vue&type=template&id=13429420&scoped=true&thisrequestWhat will beloaderAnd how?

(/lib/plugin-webpack4.js)[github.com/vuejs/vue-l… webpack V4)

class VueLoaderPlugin {
  apply (compiler) {
    // ...
    // global pitcher (responsible for injecting template compiler loader & CSS post loader)
    const pitcher = {
      loader: require.resolve('./loaders/pitcher'),
      resourceQuery: query= > {
        const parsed = qs.parse(query.slice(1))
        returnparsed.vue ! =null
      },
      options: {
        cacheDirectory: vueLoaderUse.options.cacheDirectory,
        cacheIdentifier: vueLoaderUse.options.cacheIdentifier
      }
    }
    compiler.options.module.rules = [
      pitcher,
      // other rules ....     ]}}Copy the code

After webPack generates the compiler, we inject the pitcher-loader, which we focus on with the matching rule resourceQuery. We often use the test: /\.vue$/, which is normalized by RuleSet within WebPack. Therefore, the above request will be processed by the pitch function in the pitch-loader first. (specific reason reference documentation) [www.webpackjs.com/api/loaders… webpack caused by the design of the loader in mechanism, we do not discuss here.

So we turn our attention to the pitchpitch function of the pitcher-loader, which is simplified as follows:

const stylePostLoaderPath = require.resolve('./stylePostLoader')
module.exports.pitch = function (remainingRequest) {
  if (query.type === `template`) {
    // ...
    const cacheLoader = // ...
    const preLoaders = loaders.filter(isPreLoader)
    const postLoaders = loaders.filter(isPostLoader)
    const request = genRequest([
      ...cacheLoader,
      ...postLoaders,
      templateLoaderPath + `?? vue-loader-options`. preLoaders ])// console.log('pitcher template', request)
    // the template compiler uses esm exports
    return `export * from ${request}`
  }
  // ...
}
Copy the code

The main task is to find the loaders in the current module match, sort them, and add the corresponding block processing loader, such as templateLoader here, and then generate our latest request through genRequest. -! ./lib/vue-loader/loaders/templateLoader.js?? vue-loader-options! ./lib/vue-loader/index.js?? vue-loader-options! ./test.vue? Vue&type = template&id = 13429420 & scoped = true &. There are a few things to note in this request.

  1. At the beginning of-!This symbol, this symbol tellswebpackI’m dealing with thisrequest ignoreAll common and pre-configuredloader. (See here for details)[www.webpackjs.com/configurati…]
  2. In the middle of the!The symbol is used to divideloader.
  3. This use aboveloaderWay,inline(See here for details)[www.webpackjs.com/concepts/lo…]

The third stage

As the graph shows,

In order to get the aboverequestAfter that,webpackWill be usedvue-loaderProcess and then usetemplate-loaderTo get the final module. Let’s look at the code and see what they do.

module.exports = function (source) {
  // source is the source file read from test.vue
  const loaderContext = this
  const { resourceQuery = ' ' } = loaderContext
  const rawQuery = resourceQuery.slice(1)
  const inheritQuery = ` &${rawQuery}`
  const incomingQuery = qs.parse(rawQuery)
  // Convert the test.vue file into a file descriptor using the @vue/component-compiler-utils parse parser
  // The compiler parameter is the Vue-template-compiler template parser
  const descriptor = parse({
    source,
    compiler: options.compiler || loadTemplateCompiler(loaderContext),
    filename,
    sourceRoot,
    needMap: sourceMap
  })
  // if the query has a type field, this is a language block request
  // e.g. foo.vue? type=template&id=xxxxx
  // and we will return early
  // If the query has a type field, this is a block request
  / / such as foo vue? Type = template&id = XXXXX return as soon as possible
  // We need to pay attention to the return statement in loader, because multiple Loaders are chained, this exit logic will be used in the third phase, we will not discuss in the first phase
  if (incomingQuery.type) {
    returnselectBlock( descriptor, loaderContext, incomingQuery, !! options.appendExtension ) }// ...
}
Copy the code

Function we need to pay attention to its exit, here is the vue-Loader’s second exit, we know from the code comment that when vue-Loader is processing a block request in the.vue file, it serializes the fast request parameter by qs.parse? Vue&type =template&id=13429420&scoped=true &returns the result of selectBlock if type is present. Let’s see what the selectBlock does.

module.exports = function selectBlock (descriptor, loaderContext, query, appendExtension) {
  // template
  if (query.type === `template`) {
    if (appendExtension) {
      loaderContext.resourcePath += '. ' + (descriptor.template.lang || 'html')}// Tip: pass to the next loader
    loaderContext.callback(
      null,
      descriptor.template.content,
      descriptor.template.map
    )
    return}}Copy the code

The selectBlock passes the part of the descriptor to the next loader(templation-loader) via loaderContext.callback.

// templateLoader.js
const { compileTemplate } = require('@vue/component-compiler-utils')
module.exports = function (source) {
  const loaderContext = this
  const compiler = options.compiler || require('vue-template-compiler')
  const compilerOptions = Object.assign({
    outputSourceRange: true
  }, options.compilerOptions, {
    scopeId: query.scoped ? `data-v-${id}` : null.comments: query.comments
  })
  // for vue-component-compiler
  const finalOptions = {
   // ...
  }
  const compiled = compileTemplate(finalOptions)
  // tips
  // ...
  const { code } = compiled
  return code + `\nexport { render, staticRenderFns }`
}
Copy the code

Template-loader compiles the template portion of the.vue file into functions via the custom or built-in compileTemplate, which is what modules in vUE parse to provide vUE runtime performance. After all, template parsing is a performance consuming process. The returned product looks something like this:

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c("div", [
    _c("span", { staticClass: "haha" }, [
      _vm._v("\n " + _vm._s(_vm.msg) + "\n ")]])}var staticRenderFns = []
render._withStripped = true
Copy the code

The result of the render function is vNode.

conclusion

  1. Through the upper facetemplateWe knowvue-lodaerHow to deal with.vueFile, for othersblockAlso please explore, for examplestyleAnd among themlang=sassHow to deal with it.
  2. Further deepen our understanding ofwebpack loaderThis helps us to customizeloaderTo process the files.
  3. A new understanding of how compile time and run time are managed.