Frontend Development 17 min read

Understanding How vue-loader Processes Vue SFC Files and Reuses Other Loaders

This article explains the internal workflow of vue-loader, detailing how it separates style, script, template, and custom blocks in Vue single‑file components, injects a pitcher loader, clones webpack rules, and leverages resourceQuery to reuse user‑defined loaders such as less‑loader or babel‑loader.

ByteFE
ByteFE
ByteFE
Understanding How vue-loader Processes Vue SFC Files and Reuses Other Loaders

Overview

Vue SFC files contain multiple sections—style, script, template, and custom blocks. vue-loader handles each section separately through a normal loader, a pitcher loader, and a plugin that dynamically modifies the webpack configuration.

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

Plugin Pre‑processing Stage

The VueLoaderPlugin injects a pitcher loader at the beginning of the rule list and clones the existing rules so that they can be applied to the generated virtual resources. The pitcher defines a resourceQuery function that matches paths like *.vue?vue .

class VueLoaderPlugin {
  apply(compiler) {
    const rules = compiler.options.module.rules;
    const clonedRules = rules
      .filter(r => r !== rawVueRules)
      .map(rawRule => cloneRule(rawRule, refs));
    const pitcher = {
      loader: require.resolve('./loaders/pitcher'),
      resourceQuery: query => {
        if (!query) return false;
        const parsed = qs.parse(query.slice(1));
        return parsed.vue != null;
      }
    };
    compiler.options.module.rules = [pitcher, ...clonedRules, ...rules];
  }
}

function cloneRule(rawRule, refs) {
  // ... clone logic ...
}

After injection the rule list contains six entries: the pitcher for Vue files, cloned CSS and JS rules, the original Vue rule, the original CSS rule, and the original JS rule.

SFC Content Processing Stage

Step 1 – First vue-loader Execution

The normal loader parses the SFC with @vue/component-compiler-utils , extracts the blocks, and generates import statements that point to virtual resources with query parameters.

import { render, staticRenderFns } from "./index.vue?vue&type=template&id=2964abc9&scoped=true&";
import script from "./index.vue?vue&type=script⟨=js&";
import style0 from "./index.vue?vue&type=style&index=0&id=2964abc9&scoped=true⟨=css&";

Step 2 – Pitcher Execution

The pitcher receives the virtual request, parses the resourceQuery , and rewrites the import path to include the appropriate loaders defined by the cloned rules (e.g., babel-loader , css-loader , less-loader ).

module.exports = function(code) { return code; };
module.exports.pitch = function(remainingRequest) {
  const query = qs.parse(this.resourceQuery.slice(1));
  let loaders = this.loaders;
  // handle eslint, null-loader, style‑post‑loader, etc.
  const request = genRequest(loaders);
  return `import mod from ${request}; export default mod; export * from ${request}`;
};

Step 3 – Second vue-loader Execution

When the rewritten request contains a type parameter, vue-loader’s selectBlock function returns the raw content of the requested block (template, script, style, or custom).

module.exports = function(source) {
  const descriptor = parse({ source, ... });
  if (incomingQuery.type) {
    return selectBlock(descriptor, loaderContext, incomingQuery, !!options.appendExtension);
  }
  // ... other logic ...
};

function selectBlock(descriptor, loaderContext, query, appendExtension) {
  if (query.type === 'template') { /* return template content */ }
  if (query.type === 'script') { /* return script content */ }
  if (query.type === 'style' && query.index != null) { /* return style content */ }
  if (query.type === 'custom' && query.index != null) { /* return custom block */ }
}

Summary of the Two Original Questions

Vue-loader adds different query parameters to the original file path; the pitcher’s resourceQuery then matches those parameters and routes each block to the appropriate loader chain, making the processing logic clear and modular.
After the normal loader and pitcher stages, the SFC is transformed into import statements like -!babel-loader!vue-loader?... , which allows vue-loader to reuse any user‑configured loader (e.g., less-loader for a lang="less" style block).

Additional Takeaways

Webpack plugins can dynamically modify the configuration, such as injecting a pitcher loader.

A loader does not always have to transform file content; it can simply emit a new request path to delegate work to other loaders.

Using resourceQuery enables precise matching of virtual resources generated by other loaders.

Recruitment Hard Advertisement

Urgent hiring! We are the ByteDance game frontend team, responsible for high‑traffic platforms across Toutiao, Douyin, and more, as well as a creator service platform with millions of daily active users. The team works on mini‑programs, H5, Node, and other technologies. Interested candidates can read the original article or email [email protected] .

Follow "ByteFE" for more updates.

Welcome to follow "Byte Frontend ByteFE" and click the original link to join us!

frontendJavaScriptVueWebpackloaderSFC
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login 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.