background

August 6, 2021

Vue Single File Components for Shopware 6

Yireo Blog Post

Shopware 6 ships with Vue, so people say. But as soon as they find out that Shopware implements Vue a bit differently, they start wondering why you can't use Vue SFC (Single File Components). Well, you can. Here's what, why and how.

Vue SFCs

First of all, a quick peek at Vue SFC (Single File Components). Vue is a JS framework that makes it easier to create complex UIs by using Vue components. These Vue components ship with a data layer (created either through the Options API or the Composition API) and a template, which combines HTML with JS logic. Now, a component could ship with an inline template argument (which is not really useful when the amout of HTML grows), it could ship with a separate template file (like the Twig templates with Shopware 6) or you could use a SFC.

With an SFC, you'll have a component contained in a single file - ending with *.vue - that includes the HTML template (encapsulated in a <template></template> tag), a data part (<script></script>) and an optional CSS part (<style></style>). Because a single file is used to manage the entire component, the SFC pattern has become popular amongst Vue developers. So popular that many Vue developers don't even know about alternatives to SFC.

Vue SFCs in the Shopware 6 Storefront

So let's say you know a bit about Vue and you want to include Vue SFCs in the Shopware storefront. How to do that? Well, that's a trick question! The regular Storefront of Shopware 6 is based on Twig (the PHP-version, not the JS-version) and only small bits of JS. And it doesn't include Vue by default. Want to use Vue? Then you might as well use Shopware PWA (aka Vue Storefront Next) which is entirely based on Vue. And SFCs.

Vue SFCs in the Shopware 6 Administration

So this blog is actually only focused upon the Shopware 6 Administration, which uses Vue quite a bit: The entire Administration is one large SPA, where the main Vue app communicates with the Shopware backend via the Admin API (REST/JSON calls). However, whenever Vue components are created, they are created via a separate Shopware.Component API (which allows for other tricks to be played like dependency injection and specific Shopware options). But you would normally not be creating SFCs. In other words, files ending .vue are not picking up. They actually lead to a compilation error, because the Webpack compiler does not know how to treat the file extension.

However, you can extend upon the entire JS application of the Shopware Administration in various ways. And one way is to add your own Webpack configuration to your custom Shopware plugin. This is the key to adding SFC anyway.

Adding SFC the SFC way

Let's see how to add SFC support to a regular app (outside of the scope of Shopware). Webpack is extensible in various ways and two of those mechanisms - plugins and loaders - are needed for adding SFC. The vue-loader takes care of both and the online instructions tell you to go through the following steps:

Install the packages vue-loader and vue-template-compiler (the latter only if it hasn't been installed already). Next, the Webpack configuration webpack.config.js needs to be modified to add a new loader rule and to add the plugin:

const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  module: {
    rules: [
      // ... other rules
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}

And you're done. It's so simple. Or is it?

Adding SFC to the Shopware Administration

Let's review the same procedure but then in the Shopware Administration. First of all, the problem is that the Webpack configuration and the NPM packages are part of the shopware/administration package. So editing them directly classifies as a core hack and is not what you want.

Instead, Shopware allows for plugins to extend upon the Webpack configuration by adding a file src/Resources/app/administration/build/webpack.config.js to your own plugin:

module.exports = () => {
    return {};
};

The configuration object that is being returned is merged into the global Webpack configuration. You can even add NPM packages to your folder src/Resources/app/administration (so, within a folder src/Resources/app/administration/node_modules) and use them in your Webpack configuration.

Unfortunately, copying the plain vue-loader configuration into your Shopware plugin will not work. For various reasons.

It doesn't work like that

First of all, the plugins Webpack configuration is loaded on top of the cores Webpack configuration which means that additional loading rules in your plugin will be appended to the core rules. And there's a huge issue with those core rules: They include matches for SFC file (*.vue) while not interpreting them as valid Vue SFC but still compiling them in a different way. And the Vue loader (which brings in actual SFC support) is built to detect these rules and see if they make sense. And they don't. We need to hack those rules to exclude Vue files.

Second, because of some other rules again picking up upon the earlier rules (ES linting, ES5 transpilation), the Vue Loader rule needs to be prepended, not appended, to the rules. Just returning a new Webpack configuration doesn't work. Fortunately, the Webpack configuration of a plugin consists of a callback and that callback receives an argument env which contains the current configuration (from the core and extended by previous plugins).

Third, the default Vue Loader loader configuration (I think I'm phrasing this correctly) allows for Hot Module Reloading and SSR while the Shopware Administration doesn't use this. I tried enabling this anyway, but this brought me further from home. Luckily the Vue Loader simply contains some flags (hotReload and optimizeSSR) that could be disabled.

The end result

All in all, the Webpack configuration is different than normal because of its tasks: It needs to remove the *.vue match from rules that don't make sense. It needs to prepend the actual Vue Loader rule. And it needs to add the plugin. The resulting Webpack configuration looks as follows:

const { VueLoaderPlugin } = require("vue-loader");

module.exports = (env) => {
    env.config.module.rules.forEach((rule) => {
        if ('foobar.vue'.match(rule.test)) {
            const newTest = rule.test.source.replace('|vue', '');
            rule.test = new RegExp(newTest);
        }
    });

    env.config.module.rules.unshift({
        test: /\.vue$/,
        loader: 'vue-loader',
        exclude: /node_modules/,
        options: {
            hotReload: false,
            optimizeSSR: false
        }
    });

    env.config.plugins.unshift(new VueLoaderPlugin);
    return {};
}

Note that you still need to add the NPM dependencies.

Even easier: A pre-built Yireo plugin

We can make this even easier. I've built a Shopware 6 plugin that allows all of this to be installed within a few steps. See the sources for further instructions: https://github.com/yireo-shopware6/YireoVueSfc

Posted on August 6, 2021

About the author

Author Jisse Reitsma

Jisse Reitsma is the founder of Yireo, extension developer, developer trainer and 3x Magento Master. His passion is for technology and open source. And he loves talking as well.

Sponsor Yireo

Looking for a training in-house?

Let's get to it!

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.

Do not miss out on what we say

This will be the most interesting spam you have ever read

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.