powerbuoy
powerbuoy

Reputation: 12838

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.

Upvotes: 0

Views: 1374

Answers (2)

Daniel
Daniel

Reputation: 35684

Make sure you have <style lang="scss"> in your SFC

You can also try deleting the package-lock and node_modules folder and do a clean install. Sometimes that can resolve an issue if the dependencies are not using compatible versions.


Update

To import using glob style imports you may need to use import-glob

https://www.npmjs.com/package/import-glob

You can also achieve similar result using global component registration. This is documented well in the official docs at: https://v2.vuejs.org/v2/guide/components-registration.html#Automatic-Global-Registration-of-Base-Components

Upvotes: 1

powerbuoy
powerbuoy

Reputation: 12838

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.

Upvotes: 1

Related Questions