Shopware is very extendable, but not everything is as easy to do or is as well documented as we would sometimes like. To extend this online ‘community-documentation’, I like to write down some tips, tricks and code snippets and share them with the world. Today, I tried to add a custom icon to the Shopware 6 Administration, and found out that it was not very easy to do. Until you know how, of course!
I assume you already have your plugin with some administration code. This could be a custom module, or a change to an existing module. We will be extending the webpack config, we will also need to actually load our svg’s to be usable in the sw-icon component.
I assume you will put your svg icons in src/Resources/app/administration/src/app/assets/icons/svg
,
which mimics the path the icons are in in the core.
The sw-icon
component from the Shopware core dictates a naming convention for your file
names. They need to start with icons-vendor-icon-name.svg
, for example
icons-rune-icon.svg
.
This will make it possible to load our icon with <sw-icon
name="rune-icon"></sw-icon>
in our code.
Luckily, Shopware provides a way to extend the webpack config from a plugin. This happens with a config file, which the build scripts from Shopware will automatically pick up, after which it applies its config.
To get started, create a file `src/Resources/app/administration/build/webpack.config.js.
First of all, lets start with this small boilerplate code. This imports the needed dependencies, and exports a function which will extend the default config.
const { resolve, join } = require('path');
module.exports = ({ config }) => {
// Add some magic here
};
Our end goal, and the reason why we need to extend the webpack config, is to include our svg icons inline. To do this, there are 2 things we need to do. First we need to remove (or ‘exclude’) our svg’s path from a certain rule (the url-loader rule which will make a base64 string, which we don’t want), and then add our path to another rule (the svg-inline-loader rule which… well loads svgs inline :-P).
For the first path, we will need to find the url-loader rule.
// Find the url loader rule
const urlLoaderRule = config.module.rules.find((rule) => {
return rule.loader === 'url-loader';
});
When we found the rule, we will add our directory to the excludes
parameter of
the rule.
// Add our svg icons
urlLoaderRule.exclude.push(
resolve(join(__dirname, '../src/app/assets/icons/svg'))
);
Finally, we add a new svg-inline-loader
rule to the configuration.
return {
module: {
rules: [
{
test: /\.svg$/,
include: [
resolve(join(__dirname, '../src/app/assets/icons/svg'))
],
loader: 'svg-inline-loader',
options: {
removeSVGTagAttrs: false
}
},
]
}
};
So quick recap: We change the existing config (by adding something to an existing rule), and add some more config by adding the new rule. Here’s the complete file:
const { resolve, join } = require('path');
module.exports = ({ config }) => {
// Find the url loader rule
const urlLoaderRule = config.module.rules.find((rule) => {
return rule.loader === 'url-loader';
});
// Add our svg icons
urlLoaderRule.exclude.push(
resolve(join(__dirname, '../src/app/assets/icons/svg'))
);
return {
module: {
rules: [
{
test: /\.svg$/,
include: [
resolve(join(__dirname, '../src/app/assets/icons/svg'))
],
loader: 'svg-inline-loader',
options: {
removeSVGTagAttrs: false
}
},
]
}
};
};
The way the Shopware icons work, is by creating every icon as a Vue component. We won’t be making a component manually. You could, but that’s a lot of boiler plate copy-paste code, so we reuse the generator that Shopware core uses.
Create the following file
src/Resources/app/administration/src/app/assets/icons/icons.js
in your plugin:
export default (() => {
const context = require.context('./svg', true, /svg$/);
return context.keys().reduce((accumulator, item) => {
const componentName = item.split('.')[1].split('/')[1];
const component = {
name: componentName,
functional: true,
render(createElement, elementContext) {
const data = elementContext.data;
return createElement('span', {
class: [data.staticClass, data.class],
style: data.style,
attrs: data.attrs,
on: data.on,
domProps: {
innerHTML: context(item),
},
});
},
};
accumulator.push(component);
return accumulator;
}, []);
})();
What this file does is the following:
context
object for the svg folder. This is the folder that holds your
svg files.
.svg
file extensions. This makes the ‘raw’ filename the component name.
context(item)
function.
It’s not just done yet. This file will not be automagically called, so we still have to
import & run the file.
To do this, open your main.js
file and add the following:
import iconComponents from './app/assets/icons/icons';
iconComponents.map((component) => {
return Shopware.Component.register(component.name, component);
});
This imports our icons.js
file, runs it to generate the components, and
registers every component to the Shopware component registry.
Now that I write it here, it’s actually quite simple. 3 files and you can get started with
your own custom icons.
You can now use the sw-icon
component to include your shiny custom icons.
Are your icons still not loading? Maybe you have one of the issues I encountered:
Make sure your icon components (= the svg file name) start with icons-
. This is
a prefix that is used by the sw-icon
component.
Filename icons-rune-test.svg
will become <sw-icon
name="rune-test"></sw-icon>
Are you seeing a base64 string? This means something is wrong with the webpack config, and
your custom config is probably not loading correctly.
Did you dump the plugin configuration? No? Run bin/console bundle:dump
, restart your
watcher/build and it should work.
Are you sure you have restarted your watcher? No? Restart bin/watch-administration.sh
and
it should work.
I hope nobody needs to spend as much time as I did to figure a simple thing like this out. If you were helped by this guide, consider buying me a coffee. Did you already figure this out yourself, and are you looking for a job? Contact me!