Kontakt Architektur-Review anfragen
Dev TalkFrontend Development

So verwaltest du mehrere Themes in einem Pimcore-Projekt mit dem CoreShop Theme Bundle

Das CoreShop Theme Bundle bietet eine standardisierte Lösung, um Themes in Pimcore zu bauen und zu verwalten, auch wenn man die E-Commerce-Funktionen von CoreShop gar nicht nutzt. Damit passt es zu vielen Pimcore-Projekten, von einfachen Content-Websites bis zu komplexen Multi-Site-Installationen.

Bildschirm mit einer Website-Komponentenbibliothek und dem CoreShop-Logo

Warum braucht man überhaupt mehrere Themes in Pimcore?

In manchen Projekten betreiben wir 20 oder 30 Websites innerhalb einer einzigen Pimcore-Instanz. Das kommt vor, wenn mehrere Sites oder Landingpages dasselbe Datenmodell teilen, etwa:

  • Microsites oder Marketingseiten mit unterschiedlichen Layouts
  • Verschiedene visuelle Themen für Web- und Printausgaben
  • Getrennte Portale, die aber denselben Backend-Datenbestand nutzen

Das Theme Bundle von CoreShop sorgt für eine saubere und skalierbare Architektur, mit der solche Setups leichter zu managen sind.

So installierst und konfigurierst du das Theme Bundle

Wir setzen auf maximale Flexibilität und Wiederverwendbarkeit: Ein „Basis-Theme“ enthält allgemeine Funktionalitäten und Styles, die dann in projektspezifischen Themes erweitert werden.

1. Installation des CoreShop Theme Bundles

Führe folgenden Befehl via Composer aus:

Shell
composer require coreshop/theme-bundle:^4.0

Anschließend aktivierst du es in deiner Kernel.php:

PHP
public function registerBundlesToCollection(BundleCollection $collection)
{
    $collection->addBundles([
        new \CoreShop\Bundle\ThemeBundle\CoreShopThemeBundle(),
    ]);
}

2. Theme Resolver konfigurieren

CoreShop liefert mehrere „Theme Resolver“ mit. Zum Beispiel kannst du Themes über Pimcore-Dokumenteigenschaften zuweisen:

YAML
core_shop_theme:
   default_resolvers:
     pimcore_document_property: true

Dazu weist du eine Dokumenteigenschaft namens theme zu. CoreShop versucht dann, das passende Theme anhand dieses Namens (z. B. base) aufzulösen.

Pimcore-Dokument, Reiter Navigation & Properties: Eigenschaft theme mit dem Wert cors/base

Verzeichnisstruktur der Themes in Pimcore

Intern greift CoreShop auf das Sylius Theme Bundle zurück und nutzt das Dateisystem als Konfigurationsquelle. Standardmäßig sucht es in %kernel.project_dir%/themes nach composer.json-Dateien.

Verzeichnisbaum eines Projekts mit den Theme-Ordnern unter themes/

Jedes Theme enthält alle notwendigen Assets wie Twig-Templates, JS, CSS, Bilder und Schriften in einem eigenen Verzeichnis.

Theme-Vererbung mit Sylius

Das Sylius Theme Bundle unterstützt Theme-Vererbung. Ein Theme kann also von einem anderen Theme erben, zum Beispiel über eine composer.json-Datei:

JSON
{
  "name": "cors/portal",
  "description": "CORS portal theme",
  "authors": [
    {
      "name": "CORS GmbH",
      "email": "office@cors.gmbh",
      "homepage": "https://www.cors.gmbh/",
      "role": "TEAM"
    }
  ],
  "extra": {
    "sylius-theme": {
      "title": "portal",
      "parents": [
        "cors/base"
      ]
    }
  }
}

Damit kannst du viel Redundanz vermeiden und Themes modular gestalten.

Theme-basierte Assets in Pimcore verwalten

Alle Assets, die zu einem Theme gehören, liegen in den jeweiligen Theme-Ordnern. Die Build-Tools (z. B. Webpack Encore) werden entsprechend konfiguriert, um pro Theme CSS, JS etc. zu generieren.

Webpack-Encore-Konfiguration

Jedes Theme erhält ein eigenes Build-Ziel:

YAML
webpack_encore:
   output_path: '%kernel.project_dir%/public/build/base'
   builds:
     admin: '%kernel.project_dir%/public/build/admin'
     base: '%kernel.project_dir%/public/build/base'
     # Beispiel für zusätzliches Theme
     # portal: '%kernel.project_dir%/public/build/portal'

Symfony Asset Packages

Danach konfigurierst du noch die Symfony Asset Packages so, dass sie die Manifest-Dateien korrekt referenzieren und die passenden Pfade für jedes Theme nutzen.

YAML
framework:
   assets:
       json_manifest_path: '%kernel.project_dir%/public/build/base/manifest.json'
       packages:
           admin:
               base_path: '/build/admin/'
               json_manifest_path: '%kernel.project_dir%/public/build/admin/manifest.json'
           base:
               base_path: '/build/base/'
               json_manifest_path: '%kernel.project_dir%/public/build/base/manifest.json'
           # portal:
           #     base_path: '/build/portal/'
           #     json_manifest_path: '%kernel.project_dir%/public/build/portal/manifest.json'

Mit dieser Konfiguration werden die Asset-Pfade dynamisch aufgelöst. Das kann z. B. eine Twig-Funktion (themeName) erledigen:

Twig
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('images/favicons/apple-touch-icon.png',cors_theme.themeName) }}">

Eigene Webpack-Configs pro Theme

Für jedes Theme definierst du idealerweise eine eigene Webpack-Konfigurationsdatei, z. B. über eine Hilfsfunktion getConfig:

JavaScript · webpack.config.js
const Encore = require('@symfony/webpack-encore');
/**
 *
 * @param {string} name
 * @param {string} outputPath
 * @param {string} publicPath
 * @param {Array.<{name:string, src:string}>} entries
 * @param {Array.<{name:string, src:string}>} styleEntries
 * @param {Array.<{from:string, to:string}>} copyFiles
 * @param {Array.<Plugin>} plugins
 * @param {boolean} defaults
 * @param {boolean} mangle
 * @returns {webpack.Configuration}
 */
function getConfig({
                       name,
                       outputPath,
                       publicPath,
                       entries = [],
                       styleEntries = [],
                       copyFiles = [],
                       plugins = [],
                       defaults = true,
                       mangle = true
                   }) {
    Encore.reset();
    if (!mangle) {
        Encore.configureTerserPlugin((options) => {
            return {
                terserOptions: {
                    mangle: false,
                }
            };
        })
    }
    Encore
    // directory where all compiled assets will be stored
    .setOutputPath(outputPath)
    // what's the public path to this directory (relative to your project's document root dir)
    .setPublicPath(publicPath)
    .cleanupOutputBeforeBuild()
    // allow sass/scss files to be processed
    .enableSassLoader()
    .enablePostCssLoader((options) => {
        options.postcssOptions = {
            path: 'postcss.config.js'
        };
    })
    .enableSingleRuntimeChunk()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
    .enableTypeScriptLoader()
    ;
    // ##########################################################################################
    // ### default integrations for all configs
    // ##########################################################################################
    if (defaults) {
        // Put your default js includes here, if needed
        /*Encore
        .addEntry('js/base', './themes/base/assets/js/base.js')
        ;*/
    }
    entries.forEach(entry => {
        if (entry.name && entry.src) {
            Encore.addEntry(entry.name, entry.src);
        }
    })
    styleEntries.forEach(entry => {
        if (entry.name && entry.src) {
            Encore.addStyleEntry(entry.name, entry.src);
        }
    })
    if (defaults) {
        // Put your default copyFiles here, if needed
        /*copyFiles = [
            ...[
                {
                    from: './themes/base/assets/images',
                    to: 'images/[path][name].[ext]',
                }, {
                    from: './themes/base/assets/fonts',
                    to: 'fonts/[path][name].[ext]',
                }
            ],
            ...copyFiles
        ];*/
    }
    Encore
    .copyFiles(copyFiles)
    plugins.forEach(plugin => {
        Encore.addPlugin(plugin);
    })
    const config = Encore.getWebpackConfig();
    config.name = name;
    return config;
}
// export the final configuration
const admin = getConfig({
    name: 'admin',
    outputPath: 'public/build/admin',
    publicPath: '/build/admin',
    defaults: false,
    mangle: false,
    styleEntries: [
        {
            name: 'css/admin/edit',
            src: './themes/base/admin/assets/css/edit.scss'
        }
    ],
    copyFiles: [
        {
            from: './themes/base/admin/assets/js',
            to: 'js/[path][name].[ext]',
        },
    ]
});
const base = getConfig(require('./themes/base/webpack-web.config.js'));
const portal = getConfig(require('./themes/portal/webpack-web.config.js'));
module.exports = [admin, base, portal];

Eine themespezifische Config sieht dann z. B. so aus. Dadurch kannst du Themes unabhängig voneinander bauen und alle Abhängigkeiten sauber trennen.

JavaScript · themes/base/webpack-web.config.js
module.exports = {
    name: 'base-web',
    outputPath: 'public/build/base',
    publicPath: '/build/base',
    plugins: [
        require('./purgecss-web.config.js')
    ],
    entries: [
        {
            name: 'js/project',
            src: './themes/base/assets/js/web.ts'
        },
        ...
    ],
    styleEntries: [
        {
            name: 'css/project',
            src: './themes/base/assets/css/web.scss'
        }
    ],
    copyFiles: [
        {
            from: './themes/base/assets/images',
            to: 'images/[path][name].[ext]',
        }, {
            from: './themes/base/assets/fonts',
            to: 'fonts/[path][name].[ext]',
        }
    ],
}

Fazit: Bereit für themebasierte Pimcore-Projekte

Mit dem CoreShop Theme Bundle lassen sich skalierbare, modulare und wiederverwendbare Themes für Pimcore-Projekte umsetzen, auch ganz ohne E-Commerce-Funktionalität. Das eignet sich besonders für Multi-Site-Architekturen, Portale und Content-Projekte.

Wenn du Unterstützung beim Aufsetzen deiner Theme-Struktur brauchst, melde dich gern. Weitere Details findest du in der offiziellen Dokumentation.

Georg Wurz
Geschrieben von

Georg Wurz

Technical Lead

Dominik Pfaffenbauer

Dominik Pfaffenbauer

Geschäftsführung & Solution Architecture

Noch Fragen zum Beitrag?

Schreiben Sie uns. Wir helfen bei Bundles, Datenmodell und Architektur Ihrer Pimcore-Plattform.