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.
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!
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.