Skip to content
字数
1018 字
阅读时间
5 分钟

vue-loader 是 Webpack 的一个加载器(loader),专门用于处理 .vue 单文件组件,是连接 SFC 与构建工具的核心桥梁,主要作用如下:

  1. 拆分 SFC 代码块解析 .vue 文件的结构,将 <template><script><style> 分离为独立的代码块,分别传递给对应的 loader 处理(如 vue-template-loader 处理模板,babel-loader 处理脚本,css-loader 处理样式)。

  2. 协调各部分编译工具

    • 自动调用 Vue 的模板编译器(vue-template-compiler 或 @vue/compiler-sfc)将模板编译为 render 函数。
    • 支持样式的 scoped 和 module 特性:通过 postcss 为 scoped 样式添加作用域哈希(如 .title[data-v-xxxx]),避免样式污染;为 module 样式生成 CSS Modules 映射对象。
  3. 处理依赖与热更新

    • 分析 .vue 文件中的依赖(如 import 语句),确保 Webpack 正确追踪依赖关系。
    • 支持热模块替换(HMR):修改 .vue 文件后,仅更新对应组件,无需刷新整个页面,提升开发效率。
  4. 适配 Vue 版本与特性

    • 针对 Vue 2 和 Vue 3 提供不同的编译逻辑(如 Vue 3 中支持 <script setup> 语法糖)。
    • 处理 SFC 中的高级特性,如自定义块(<custom-block>)、预处理器(lang="scss" 等)

解析所依赖的模块

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 的职责主要是三件:

  1. 调用 @vue/component-compiler-utilsparse 函数
  2. 如果存在 loader 的参数存在 type 属性,则执行 selectBlock 函数,用于选取源码(比如从 Vue 文件中选取 template 标签中的源码,依赖与上方 parse 函数的解析结果)
  3. 根据 parse 返回结果拼接字符串,并返回

整体时序图:
https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/161f9c88c48a4dcca446191ccc88262a~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image

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 的配置。

  1. 处理 .vue 文件的 loader 配置被分离出来,存放在变量 vueLoaderUse 中。
  2. compiler.options.module.rules 中的其余规则复制到变量 clonedRules 中。
  3. 基于 vueLoaderUse 中用户设置的 options 生成一个新的规则 pitcher
  4. 重写 compiler.options.module.rules

重写后的 rules 存在两条和 Vue 相关的规则:

  1. vue-loader/lib/loaders/pitcher.js (本条是新增的)。
  2. 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'
  ]
}

贡献者

The avatar of contributor named as jiechen jiechen
The avatar of contributor named as chenjie chenjie

页面历史

撰写