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.
- Stage 1: Pass
vue-loader
将.vue
The 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
- Stage two: Pass
pitcher-loader
(theloader
Is through thevueloaderplugin
Injected into thewebpack
Convert 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&
- The third stage: the second stage transformation
request
Request, through the correspondingloader
To 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-loader
Process and then usetemplateLoader
I’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&
pitcher-loader
How is it injected intoloaders
In the?./test.vue? vue&type=template&id=13429420&scoped=true&
thisrequest
What will beloader
And 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.
- At the beginning of
-!
This symbol, this symbol tellswebpack
I’m dealing with thisrequest
ignoreAll common and pre-configuredloader
. (See here for details)[www.webpackjs.com/configurati…] - In the middle of the
!
The symbol is used to divideloader
. - This use above
loader
Way,inline(See here for details)[www.webpackjs.com/concepts/lo…]
The third stage
As the graph shows,
In order to get the aboverequest
After that,webpack
Will be usedvue-loader
Process and then usetemplate-loader
To 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
- Through the upper face
template
We knowvue-lodaer
How to deal with.vue
File, for othersblock
Also please explore, for examplestyle
And among themlang=sass
How to deal with it. - Further deepen our understanding of
webpack loader
This helps us to customizeloader
To process the files. - A new understanding of how compile time and run time are managed.