vue.jswebpackvue-loader

Implementing Vue SFC with Webpack (no Vue CLI)


I've recently switched to Webpack and have all my JS and CSS running perfectly through it now. Here's the relevant piece of webpack.config.js:

rules: [
    {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
            {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env']
                }
            },
            {loader: 'import-glob-loader'}
        ]
    },
    {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
            {loader: MiniCssExtractPlugin.loader},
            {loader: 'css-loader'},
            {
                loader: 'postcss-loader',
                options: {
                    plugins: [
                        require('autoprefixer')
                    ]
                }
            },
            {loader: 'sass-loader'},
            {loader: 'import-glob-loader'}
        ]
    }
]

I have Vue included from a CDN and with this setup I can do the following no problem:

Vue.component('test-component', {
    data: function () {
        return {
            title: 'Title',
            description: 'Description'
        };
    },
    methods: {
        created: function () {
            console.log('Created');
        }
    },
    template: '<section id="test-component"><h2>{{ title }}</h2>{{ description }}</section>'
});

new Vue({el: '#app'});

And in my HTML:

<div id="app">
    <test-component></test-component>
</div>

I'd now like to use Vue single file components instead, and reading the docs it tells me to simply run .vue files through vue-loader, so I changed my rules to the following:

rules: [
    // NOTE: This is new
    {
        test: /\.vue$/,
        loader: 'vue-loader'
    },
    {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
            {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env']
                }
            },
            {loader: 'import-glob-loader'}
        ]
    },
    {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
            {loader: MiniCssExtractPlugin.loader},
            {loader: 'css-loader'},
            {
                loader: 'postcss-loader',
                options: {
                    plugins: [
                        require('autoprefixer')
                    ]
                }
            },
            // NOTE: This is new too, but commented because it throws errors
        //  {loader: 'vue-style-loader'},
            {loader: 'sass-loader'},
            {loader: 'import-glob-loader'}
        ]
    }
]

With that in place my .vue files are picked up and added to dist/main.js so it seems to be working (as long as I don't include a <style> element in the Vue file in which case it fails), but now new Vue({el: '#app'}) does absolutely nothing. Checking the DOM the <test-component> is still in there and not rendered by Vue at all.

If I also try to enable vue-style-loader the build fails entirely saying:

(1:4) Unknown word

    > 1 | // style-loader: Adds some css to the DOM by adding a <style> tag
        |    ^
      2 | 
      3 | // load the styles

What am I doing wrong here?

Edit: Progress. Thanks to Daniel my <style> now works as long as it has lang="scss" set. This is because my webpack config only has rules for scss files and not css files.

I've also figured out the reason the <test-component> won't render is because I never actually register it, simply including the .vue-file is not enough for it to be registered obviously.

The problem I'm having now is trying to glob import all my .vue-files as an array of components. If I do this it works fine:

import TestComponent from "./test-component.vue";
import AnotherComponent from "./another-component.vue";

document.querySelectorAll('[data-vue]').forEach(el => {
    new Vue({
        el: el, 
        components: {
            'test-component': TestComponent, 
            'another-component': AnotherComponent
        }
    });
});

But I'd like to be able to do this some how:

import components from "./**/*.vue";

document.querySelectorAll('[data-vue]').forEach(el => {
    new Vue({
        el: el, 
        components: components
    });
});

Using import-glob-loader.


Solution

  • Simply importing the vue files is not enough for them to be available for use. You also have to register them with Vue.

    So, this is wrong:

    import 'component.vue';
    
    new Vue({el: '#app'});
    

    This is right:

    import component from 'component.vue';
    
    new Vue({
        el: '#app', 
        components: {
            'component': component
        }
    });
    

    That takes care of making them usable.

    The reason the <style> elements don't work is because I don't have a webpack rule for CSS files - only for SCSS files. Thanks to @Daniel for pointing out that I need <style lang="scss">.

    vue-style-loader is only needed to inject styles into the DOM as <style> elements which I don't actually use (I use mini-css-extract-plugin to create css-files) also according to the docs:

    However, since this is included as a dependency and used by default in vue-loader, in most cases you don't need to configure this loader yourself.

    Will create a separate question regarding the glob import.