Vue Loader v15相对于以前的版本,改动比较大。不仅拆分出VueLoaderPlugin,而且其Loader 推导策略完全改变了。更多详细改动可以看下官方迁移文档 (opens new window)。Loader15之前的源码解析可以看这篇文章 (opens new window),作者分析了Loader15前的源码,对比现在这篇文章会有更多体会。
Vue Loader简单讲,就是将 *.vue 文件变成可以在浏览器中运行的js模块文件。以下我们具体分析下Vue Loader v15源码。
const selectBlock = require('./select')
const { parse } = require('@vue/component-compiler-utils')
module.exports = function (source) {
// this是webpack注入的内容
const {
target,
request,
minimize,
sourceMap,
rootContext,
resourcePath,
resourceQuery
} = this
const rawQuery = resourceQuery.slice(1)
const incomingQuery = qs.parse(rawQuery)
// 通过parse解析.vue文件,获取不同的类型,如template、style、script
const descriptor = parse({
source,
compiler: options.compiler || require('vue-template-compiler'),
filename,
sourceRoot,
needMap: sourceMap
})
// 带有type字段,使用语言推导(关键代码)
// 如:foo.vue?type=template&id=xxxxx会进入下面selectBlock
// 这里第一次是直接./foo.vue,不带type;而第一次生成的code又需要进入到Vue Loader中
if (incomingQuery.type) {
return selectBlock(
descriptor,
loaderContext,
incomingQuery,
!!options.appendExtension
)
}
// 如果没有type字段,则导出code源码。
// template
let templateImport = `var render, staticRenderFns`
let templateRequest
if (descriptor.template) {
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
const attrsQuery = attrsToQuery(descriptor.template.attrs)
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
const request = templateRequest = stringifyRequest(src + query)
templateImport = `import { render, staticRenderFns } from ${request}`
}
// script
let scriptImport = `var script = {}`
if (descriptor.script) {
const src = descriptor.script.src || resourcePath
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
const request = stringifyRequest(src + query)
scriptImport = (
`import script from ${request}\n` +
`export * from ${request}` // support named exports
)
}
// styles
let stylesCode = ``
if (descriptor.styles.length) {
stylesCode = genStylesCode(
loaderContext,
descriptor.styles,
id,
resourcePath,
stringifyRequest,
needsHotReload,
isServer || isShadow // needs explicit injection?
)
}
let code = `
${templateImport}
${scriptImport}
${stylesCode}
...`
return code
}
descriptor
信息(JavaScript对象)。类似如下内容:selectBlock
这个模块进行处理。源码在select.js中:module.exports = function selectBlock (
descriptor,
loaderContext,
query,
appendExtension
) {
// loaderContext === this
// template
if (query.type === `template`) {
if (appendExtension) {
loaderContext.resourcePath += '.' + (descriptor.template.lang || 'html')
}
// 异步loader
loaderContext.callback(
null,
descriptor.template.content,
descriptor.template.map
)
return
}
// script
if (query.type === `script`) {
if (appendExtension) {
loaderContext.resourcePath += '.' + (descriptor.script.lang || 'js')
}
loaderContext.callback(
null,
descriptor.script.content,
descriptor.script.map
)
return
}
// styles
if (query.type === `style` && query.index != null) {
const style = descriptor.styles[query.index]
if (appendExtension) {
loaderContext.resourcePath += '.' + (style.lang || 'css')
}
loaderContext.callback(
null,
style.content,
style.map
)
return
}
}
可以看到其分别对template/script/style内容进行处理。处理也很简单,就是使用webpack配置的loader对相应的.html后缀/.js后缀/.css后缀进行规则匹配。这也是官方文档中说明的Loader推导变更 (opens new window),v15以前是使用loaders选项
进行loader定制,但从v15之后就不需要这样做了。