background

October 1, 2023

Removing Shopware JS plugins for real

Yireo Blog Post

The Shopware 6.5 storefront ships with almost 500Kb of JavaScript. And this is due to a large set of JS plugins of which some might not be needed in your shop. What about removing some of it? Here is a neat Webpack trick that allows you to do just that.

Shopware 6.5 already got rid of jQuery

With the Shopware 6.5 release, the good news was already that the JavaScript bundle became a lot less: jQuery was removed (and the Bootstrap scripts no longer rely on it). And in my own personal benchmarks this shaved about 200Kb off the bundle. This is definitely good.

But we can always do better. There are still a lot of JavaScript plugins loaded into the bundle, which bring in again other NPM packages. And some of them might not be needed, especially when you start adding your own stuff. For instance, what if you want to rewrite the add-to-cart behaviour or use another product slider?

The window.PluginManager is not good enough

The first option is that you simply write your own plugin (extending upon the Plugin class of Shopware) and use the PluginManager.override() method to override the original JavaScript plugin. Yet another approach is to simply register your own plugin with PluginManager.register() and remove the old JS plugin with PluginManager.deregister().

While this does something good for the plugins in runtime, it does not effect the bundle size. The original JS plugin is still imported (with all of its own imports) and tree-shaking is not powerful enough to know that an unregistered JS plugin should be excluded from the bundle.

Instead, if you want to remove something from the bundle, you will need to apply Webpack magic.

Let the magic happen

In a plugin, you can add a Resources/app/storefront/build/webpack.config.js that is automatically picked up by the global Webpack process. In this plugin configuration, you can extend the global Webpack configuration object. A common trick is here to play around with resolve aliases, to allow an additional NPM package to be installed via the plugin.

However, we can also do other things with resolvers. This would either involve a custom Webpack resolving plugin (been there, done that, not the easiest thing to do) or reuse existing Webpack plugins that help do the job.

For instance, there is the IgnorePlugin that allows you to remove (or ignore) specific imports.

Ignoring a JS plugin? Nope.

In the main.js file of the Storefront bundle, there is an import for the AddToCart plugin:

import AddToCartPlugin from './plugin/add-to-cart/add-to-cart.plugin';

With the IgnorePlugin plugin, you could remove this source alltogether, by adding the following Webpack configuration:

const path = require('path');
const webpack = require('webpack');

module.exports = () => {
    return {
        plugins: [
            new webpack.IgnorePlugin({ resourceRegExp: /src\/plugin\/add-to-cart\/add-to-cart.plugin/ })
        ],
    }
}

Pretty cool. But with one side-effect. With this configuration, the import of add-to-cart.plugin.js will simply result in null. So the value of the AddToCartPlugin variable in the main.js file of the Storefront bundle would be null too. And further along, the PluginManager is called to register the AddToCart plugin, which fails because it did not expect a null value as plugin. Removing the source is a bit too dramatic.

Replacing the plugin

Meet the NormalModuleReplacementPlugin plugin. It replaces imports, instead of ignoring them. For instance, I might replace the original add-to-cart.plugin.js file with a custom version by using the following Webpack configuration:

const path = require('path');
const webpack = require('webpack');

module.exports = () => {
    return {
        plugins: [
            new webpack.NormalModuleReplacementPlugin(
                /src\/plugin\/add-to-cart\/add-to-cart.plugin/,
                path.resolve(__dirname + '/../src/plugin/fake-add-to-cart')
            )
        ],
    }
}

Next, create a file src/plugin/fake-add-to-cart.js in your own plugin:

import Plugin from 'src/plugin-system/plugin.class';

export default class FakePlugin extends Plugin {}

And now the original plugin (which might include lots of other dependencies) is never called anymore, but replaced with a very downsized version of your own. (Note that the class name in our custom plugin actually has no effect.)

Small note on Webpack

If you are using this Webpack configuration in your own plugin, note that it imports webpack (which is probably not globally available). Just to make this work, make sure to install Webpack within your plugin with the following command: npm install webpack:^5.75.

Posted on October 1, 2023

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 don't write too commercial stuff, we focus on the technology (which we love) and we regularly come up with innovative solutions. Via our newsletter, you can keep yourself up to date on all of this coolness. Subscribing only takes seconds.

Do not miss out on what we say

This will be the most interesting spam you have ever read

We don't write too commercial stuff, we focus on the technology (which we love) and we regularly come up with innovative solutions. Via our newsletter, you can keep yourself up to date on all of this coolness. Subscribing only takes seconds.