Understanding the Build Process of Vue Single‑File Components (SFC) with Webpack
The article walks through Vue’s single‑file component build pipeline with Webpack 4, detailing how vue‑loader parses .vue files into template, script, and style blocks, uses VueLoaderPlugin and pitch loaders to generate virtual modules, and ultimately bundles them via inline loader syntax.
In Vue development, Single‑File Components (SFC, .vue files) are a common way to author components. This article walks through the complete packaging workflow of an SFC using Webpack 4, helping readers understand how each part of the file is parsed and transformed, and what options are available when extending SFC capabilities.
1. Overall Overview
The build starts with vue-loader (and its companion VueLoaderPlugin ) added to a Webpack configuration. The entry point of the loader is lib/index.js , which receives the raw SFC source as the source argument and returns a string of ES‑module code that ends with export default … .
// vue-loader lib/index.js module.exports = function (source) { // ... // return value `code` is a piece of ESModule code string let code = `...`; code += `\n export default component.exports`; return code; };
When a real demo.vue file is processed, the loader rewrites the original template , script , and style blocks into separate import statements, e.g.:
import … from './demo.vue?vue&type=template'
These query strings ( ?vue&type=template , etc.) are later used by the loader chain to apply the appropriate processing steps.
2. Splitting template, script, and style blocks
After the initial pass, vue-loader calls @vue/component-compiler-utils ’s parse function to obtain a descriptor that contains the separate blocks. The loader then generates import logic for each block that exists:
// vue-loader lib/index.js const { parse } = require('@vue/component-compiler-utils'); module.exports = function (source) { // parse source, get descriptor const descriptor = parse({ source, ... }); // if template block exists if (descriptor.template) { /* ... */ } // if script block exists if (descriptor.script) { /* ... */ } // if style blocks exist (supports multiple) if (descriptor.styles.length) { /* ... */ } // if custom blocks exist if (descriptor.customBlocks && descriptor.customBlocks.length) { /* ... */ } };
These steps are illustrated in the article’s flow diagrams (Figures 1‑3).
3. Role of VueLoaderPlugin
The plugin hooks into every compilation phase, allowing it to modify module.rules dynamically. In particular, it adds a “pitcher” rule and clones existing rules so that resources with query strings like ?vue&type=template can be matched via Rule.resourceQuery .
class VueLoaderPlugin { apply (compiler) { // mark VueLoaderPlugin as loaded if (webpack4) { /* ... */ } else { /* ... */ } // modify Webpack config const rawRules = compiler.options.module.rules; const { rules } = new RuleSet(rawRules); // replace initial module.rules with pitcher, clonedRules, and original rules compiler.options.module.rules = [ pitcher, ...clonedRules, ...rules ]; } }
Using Rule.resourceQuery enables matching on the query part of a request, which is essential for handling the virtual modules generated by vue-loader .
4. Webpack loader execution order
Loaders are executed from right to left in the normal phase, but their pitch methods are called from left to right. An example configuration shows the order a‑loader → b‑loader → c‑loader . The article also demonstrates how a non‑empty return value from a pitch method can short‑circuit later loaders.
// Example loader chain module.exports = { module: { rules: [{ use: ['a-loader', 'b-loader', 'c-loader'] }] } }; // Pitch order illustration |- a-loader `pitch` | |- b-loader `pitch` | |- c-loader `pitch` | |- requested module is picked up as a dependency | |- c-loader normal execution | |- b-loader normal execution |- a-loader normal execution
5. PitchLoader implementation
The PitchLoader (named pitcher.js ) intercepts any request that contains ?vue . It builds a new request string that chains the required loaders (e.g., -!babel-loader!vue-loader ) and generates import statements for the specific block.
// vue-loader lib/loaders/pitcher.js module.exports.pitch = function (remainingRequest) { // e.g. ./demo?vue&type=script⟨=js // `this.loaders` contains all loaders that can handle .vue and its sub‑languages let loaders = this.loaders; // ... generate request string const genRequest = loaders => { /* ... */ }; // handle style and template blocks if (query.type === 'style') { /* ... */ } if (query.type === 'template') { /* ... */ } // handle script and custom blocks return `import mod from ${request}; export default mod; export * from ${request}`; };
6. Second pass of vue‑loader
After the pitch phase, the request reaches vue-loader again, but this time its job is only to extract the source of the requested block (template, script, or style) and return it to the subsequent loaders (e.g., babel-loader for scripts).
// vue-loader lib/index.js (second pass) module.exports = function (source) { if (incomingQuery.type) { return selectBlock(...); // return the specific block’s code } }; // vue-loader lib/select.js module.exports = function selectBlock(...) { // choose and return the content of the requested block };
7. Final import syntax
The final bundle uses Webpack’s inline loader syntax to chain loaders for each block. An example of the generated import line for the script block looks like:
# Original import -!../../node_modules/babel-loader/lib/index.js??ref--2-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./demo.vue?vue&type=script⟨=js; # Part1 – disables all preLoaders and postLoaders # Part2 – babel-loader processes the JS # Part3 – vue-loader parses the .vue file # Part4 – the virtual module ./demo.vue?vue&type=script⟨=js
Conclusion
The article concludes with a complete flow diagram (Figure 7) that ties together the initial parsing, the pitch phase, the second vue‑loader pass, and the final Webpack bundle generation. It also provides reference links to the Vue SFC documentation, vue‑loader repository, and Webpack loader/plugin APIs.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
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.