字数
1018 字
阅读时间
5 分钟
vue-loader 是 Webpack 的一个加载器(loader),专门用于处理 .vue 单文件组件,是连接 SFC 与构建工具的核心桥梁,主要作用如下:
拆分 SFC 代码块解析
.vue文件的结构,将<template>、<script>、<style>分离为独立的代码块,分别传递给对应的 loader 处理(如vue-template-loader处理模板,babel-loader处理脚本,css-loader处理样式)。协调各部分编译工具
- 自动调用 Vue 的模板编译器(
vue-template-compiler或@vue/compiler-sfc)将模板编译为render函数。 - 支持样式的
scoped和module特性:通过postcss为scoped样式添加作用域哈希(如.title[data-v-xxxx]),避免样式污染;为module样式生成 CSS Modules 映射对象。
- 自动调用 Vue 的模板编译器(
处理依赖与热更新
- 分析
.vue文件中的依赖(如import语句),确保 Webpack 正确追踪依赖关系。 - 支持热模块替换(HMR):修改
.vue文件后,仅更新对应组件,无需刷新整个页面,提升开发效率。
- 分析
适配 Vue 版本与特性
- 针对 Vue 2 和 Vue 3 提供不同的编译逻辑(如 Vue 3 中支持
<script setup>语法糖)。 - 处理 SFC 中的高级特性,如自定义块(
<custom-block>)、预处理器(lang="scss"等)
- 针对 Vue 2 和 Vue 3 提供不同的编译逻辑(如 Vue 3 中支持
解析所依赖的模块
CLI4 是使用的是 vue-loader 来处理 .vue 文件,相较于 CLI3 还依赖一个 VueLoaderPlugin 的插件
vue-loader
modules/vue-loader/lib/index.js
js
const { parse } = require('@vue/component-compiler-utils')
...
module.exports = function (source) {
...
const {
target,
request,
minimize,
sourceMap,
rootContext,
resourcePath,
resourceQuery
} = loaderContext
const rawQuery = resourceQuery.slice(1)
// 获取 loader 传参
const incomingQuery = qs.parse(rawQuery)
...
const descriptor = parse({
source,
// 默认使用用户配置的 compiler
compiler: options.compiler || loadTemplateCompiler(loaderContext),
filename,
sourceRoot,
needMap: sourceMap
})
// 如果 loader 配置时有指定 type 存在
if (incomingQuery.type) {
return selectBlock(
descriptor,
loaderContext,
incomingQuery,
!!options.appendExtension
)
}
...
// 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}`
}
...
let code = `
${templateImport}
${scriptImport}
${stylesCode}
...
`
...
code += `\nexport default component.exports`
return code
}可以看到 vue-loader 的职责主要是三件:
- 调用
@vue/component-compiler-utils的parse函数 - 如果存在 loader 的参数存在
type属性,则执行selectBlock函数,用于选取源码(比如从 Vue 文件中选取 template 标签中的源码,依赖与上方parse函数的解析结果) - 根据
parse返回结果拼接字符串,并返回
整体时序图:
parseHtml 执行完毕后,Vue 文件的所有信息就都记录在了 parseComponent 的对象 sfc 中,获取到 sfc 对象后会使用其中信息进行字符串拼接,最终生成一个新的模块文件(代码)交给下一个 loader 处理
VueLoaderPlugin
让我们先来看看这个插件会做些什么
node_modules/vue-loader/lib/plugin.js
js
if (webpack.version && webpack.version[0] > 4) {
// webpack5 and upper
VueLoaderPlugin = require('./plugin-webpack5')
} else {
// webpack4 and lower
VueLoaderPlugin = require('./plugin-webpack4')
}根据 Webpack 版本匹配插件版本
node_modules/vue-loader/lib/plugin-webpack4.js
js
class VueLoaderPlugin {
apply (compiler) {
// ...
const vueLoaderUse = vueUse[vueLoaderUseIndex]
vueLoaderUse.ident = 'vue-loader-options'
vueLoaderUse.options = vueLoaderUse.options || {}
// create a cloned rule
const clonedRules = rules
.filter(r => r !== vueRule)
.map(cloneRule)
const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// replace original rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
}Webpack 插件在初始化时会执行插件(函数)原型链上的 apply 方法,而 VueLoaderPlugin.apply 方法重写了当前实例的 loaders 的配置。
- 处理
.vue文件的 loader 配置被分离出来,存放在变量vueLoaderUse中。 compiler.options.module.rules中的其余规则复制到变量clonedRules中。- 基于
vueLoaderUse中用户设置的 options 生成一个新的规则pitcher。 - 重写
compiler.options.module.rules。
重写后的 rules 存在两条和 Vue 相关的规则:
vue-loader/lib/loaders/pitcher.js(本条是新增的)。- Webpack 原始配置中的 vue-loader 和 cache-loader
可以描述为:
js
{
test: /\.vue$/,
use: [
'vue-loader/lib/loaders/pitcher.js',
]
},
{
test: /\.vue$/,
use: [
'vue-loader/lib/index.js',
'cache-loader/dist/cjs.js'
]
}