How Vue-Loader Transforms Vue SFCs: A Deep Dive into Its Internals

This article walks through the complete compilation pipeline of a Vue Single‑File Component using vue‑loader v15, examining the VueLoaderPlugin, the pitcher loader, and each processing stage with concrete code examples and configuration snippets.

Didi Tech
Didi Tech
Didi Tech
How Vue-Loader Transforms Vue SFCs: A Deep Dive into Its Internals

The article builds on two previous pieces that analyzed webpack loaders and uses the official vue-loader repository (cloned via git clone [email protected]:vuejs/vue-loader.git) as a concrete example to illustrate how vue‑loader processes Vue Single‑File Components (SFCs).

Vue‑loader Overview

Vue‑loader integrates with webpack to enable developers to write .vue files containing <template>, <script>, <style> and custom blocks. During compilation, each block is extracted and handed off to the appropriate loader (e.g., pug‑plain‑loader for Pug templates, css‑loader for styles).

Webpack Configuration

const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
  ...,
  module: {
    rules: [
      ...,
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ]
  ...
}

This snippet shows the essential module.rules entry for .vue files and the required VueLoaderPlugin that copies user‑defined rules to the language‑specific blocks inside an SFC.

VueLoaderPlugin Source Analysis

// vue-loader/lib/plugin.js
class VueLoaderPlugin {
  apply() {
    // normalize user rules
    const rawRules = compiler.options.module.rules;
    const { rules } = new RuleSet(rawRules);
    // find the rule that applies to .vue files
    let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`));
    if (vueRuleIndex < 0) {
      vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`));
    }
    const vueRule = rules[vueRuleIndex];
    const vueUse = vueRule.use;
    const vueLoaderUseIndex = vueUse.findIndex(u => /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader));
    // create pitcher loader configuration
    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
      }
    };
    // prepend pitcher loader to the rule set
    compiler.options.module.rules = [
      pitcher,
      ...clonedRules,
      ...rules
    ];
  }
}

The plugin performs three main tasks: (1) verifies that a .vue or .vue.html rule exists, (2) ensures the rule uses vue-loader, and (3) injects a custom "pitcher" loader that will later intercept block‑specific requests.

Pitcher Loader Mechanics

// vue-loader/lib/loaders/pitcher.js
module.export = code => code;
module.pitch = function () {
  const query = qs.parse(this.resourceQuery.slice(1));
  let loaders = this.loaders;
  // remove eslint-loader to avoid duplicate linting
  if (query.type) {
    if (/\.vue$/.test(this.resourcePath)) {
      loaders = loaders.filter(l => !isESLintLoader(l));
    } else {
      loaders = dedupeESLintLoader(loaders);
    }
  }
  // remove the pitcher loader itself
  loaders = loaders.filter(isPitcher);
  if (query.type === 'style') {
    const cssLoaderIndex = loaders.findIndex(isCSSLoader);
    if (cssLoaderIndex > -1) {
      const afterLoaders = loaders.slice(0, cssLoaderIndex + 1);
      const beforeLoaders = loaders.slice(cssLoaderIndex + 1);
      const request = genRequest([...afterLoaders, stylePostLoaderPath, ...beforeLoaders]);
      return `import mod from ${request}; export default mod; export * from ${request}`;
    }
  }
  if (query.type === 'template') {
    const cacheLoader = cacheDirectory && cacheIdentifier ? [
      `cache-loader?${JSON.stringify({
        cacheDirectory: path.isAbsolute(cacheDirectory) ? path.relative(process.cwd(), cacheDirectory) : cacheDirectory,
        cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
      })}`]
    ] : [];
    const request = genRequest([...cacheLoader, templateLoaderPath + '??vue-loader-options', ...loaders]);
    return `export * from ${request}`;
  }
  if (query.type === 'custom' && loaders.length === 1 && loaders[0].path === selfPath) {
    return '';
  }
  const request = genRequest(loaders);
  return `import mod from ${request}; export default mod; export * from ${request}`;
};

The pitcher loader removes redundant eslint loaders, strips itself from the loader chain, and generates new requests for style, template, or custom blocks, effectively short‑circuiting the remaining loaders and handing the block to the appropriate downstream processor.

Processing Stages

Stage 1 – VueLoaderPlugin injection : The plugin adds the pitcher loader to the rule set, preparing the pipeline for block‑level handling.

Stage 2 – First pass : Vue‑loader parses the SFC, extracts each block, and for each block creates a query‑based request (e.g., source.vue?vue&type=template&lang=pug) that is handed to the pitcher loader.

Stage 3 – Pitcher interception : Based on the type query, the pitcher loader returns a new JS module request that bypasses the remaining loaders, then the generated module is compiled (AST parsing, dependency collection).

Stage 4 – Block compilation :

Style block:

vue-loader → stylePostLoader → css-loader → vue-style-loader

Template block:

vue-loader → pug-plain-loader → templateLoader → render/staticRenderFns

Stage 5 – Final assembly : The generated JS module imports the compiled render function, script object, and style modules, passes them to componentNormalizer, and exports the normalized component.

By following these steps, a Vue SFC is transformed into a JavaScript module that webpack can bundle, demonstrating how webpack’s loader mechanism enables flexible source‑code transformations.

Conclusion

The deep dive shows that vue‑loader’s multi‑stage pipeline—driven by the VueLoaderPlugin, the pitcher loader, and block‑specific loaders—systematically breaks down an SFC, processes each part with the appropriate tooling, and reassembles the pieces into a ready‑to‑use component, illustrating the power of webpack’s loader architecture for modern frontend development.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendpluginVuewebpackloadervue-loadersfc
Didi Tech
Written by

Didi Tech

Official Didi technology account

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.