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-utils 的 parse 函数
  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

页面历史

用 ♥ 撰写