Vue’s Single File Components (SFCs) are a lie; they’re not single files at all, but rather a convention that webpack and vue-loader interpret to assemble HTML, CSS, and JavaScript on the fly.

Let’s see this in action. Imagine a simple App.vue file:

<template>
  <div id="app">

    <p>{{ message }}</p>

  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello from Vue!'
    }
  }
}
</script>

<style scoped>
p {
  color: green;
}
</style>

When webpack encounters this file, vue-loader kicks in. It parses the <template>, <script>, and <style> blocks. The <template> block is compiled into a JavaScript render function. The <script> block is processed as a standard JavaScript module, often with Babel for modern syntax. The <style> block is handled by CSS loaders (like css-loader and vue-style-loader), and if scoped is present, vue-loader injects unique attributes to ensure styles only apply to this component’s elements. Finally, all these pieces are bundled together into a single JavaScript file that the browser can execute.

The core problem SFCs solve is componentization and encapsulation. Before SFCs, you’d often have separate .vue template files, .js script files, and .css style files, making it hard to keep related code together. SFCs bring these together, improving maintainability and developer experience.

Internally, vue-loader is a webpack loader. This means it intercepts files based on their extension (.vue in this case) and transforms them into a format that webpack can understand and bundle. It uses vue-template-compiler to turn the HTML template into JavaScript render functions and babel-core (or similar) to transpile the script. For styles, it leverages the webpack loader ecosystem.

The key levers you control are primarily within your vue.config.js (if using Vue CLI) or webpack.config.js.

  • chainWebpack (Vue CLI): This is the most common way to customize vue-loader and other webpack configurations. You can add new loaders, modify existing ones, or adjust loader options. For example, to add a custom loader before vue-loader for .vue files:

    // vue.config.js
    module.exports = {
      chainWebpack: config => {
        config.module.rule('vue')
          .use('my-custom-loader')
            .loader('my-custom-loader')
            .before('vue-loader');
      }
    };
    

    This tells webpack to apply my-custom-loader to .vue files before vue-loader processes them.

  • vue-loader Options: You can pass specific options to vue-loader itself. For instance, to enable experimental features or customize compiler options:

    // vue.config.js
    module.exports = {
      chainWebpack: config => {
        config.module.rule('vue')
          .use('vue-loader')
            .loader('vue-loader')
            .options({
              // Example: Enable experimental features
              experimentalFeatures: {
                reactivityTransform: true
              }
            });
      }
    };
    

    This allows fine-grained control over how vue-loader compiles your SFCs.

  • CSS Loader Configuration: You can customize how CSS within SFCs is processed. This includes options for css-loader (like modules for CSS Modules) and vue-style-loader (or style-loader for production).

    // vue.config.js
    module.exports = {
      chainWebpack: config => {
        const vueRule = config.module.rule('vue');
        vueRule.uses.delete('vue-loader'); // Remove default
        vueRule
          .use('vue-loader')
            .loader('vue-loader')
            .tap(options => {
              // Modify vue-loader options if needed
              return options;
            })
            .end()
          .use('css-loader')
            .loader('css-loader')
            .options({
              // Example: Enable CSS Modules
              modules: {
                localIdentName: '[name]-[local]-[hash:base64:5]'
              }
            })
            .end();
      }
    };
    

    Here, we’re explicitly setting up css-loader with CSS Modules enabled, meaning you can use class names like import styles from './MyComponent.module.css'; within your <style> blocks and reference them as styles.myClass.

The most surprising truth about SFC compilation is that vue-loader doesn’t actually do the compilation itself; it’s an orchestrator. It calls out to vue-template-compiler for templates, and then uses webpack’s built-in module resolution and other loaders (like babel-loader and css-loader) to process the resulting JavaScript and CSS. vue-loader’s primary job is to parse the .vue file, extract its parts, and feed them to the appropriate tools, then reassemble the outputs into a JavaScript module.

The next hurdle is optimizing your SFCs for production, which often involves understanding how vue-loader interacts with other webpack plugins like terser-webpack-plugin for JavaScript minification and mini-css-extract-plugin for extracting CSS into separate files.

Want structured learning?

Take the full Webpack course →