Inhaltsverzeichnis
Wie kann ich mithilfe von penthouse kritisches CSS für Shopware 6 extrahieren?
Wir gehen davon aus, dass wir uns im Projekt-Verzeichnis mit funktionierender Shopware-Installation befinden und Node.js sowie npm installiert sind.
- Initialisiere ein npm-Projekt im Theme-Verzeichnis, falls noch nicht geschehen.
cd custom/plugins/FietzRevplusChild/src/Resources/app/storefront/npm init -y - Penthouse installieren.
npm i -D penthouse - Penthouse konfigurieren.
Erstelle eine Datei namensapp/storefront/critical.config.js. - Ausführungs-Script erstellen.
Erstelle eine Datei namensapp/storefront/src/critical.js. - Script zur Generierung des kritischen CSS hinzufügen.
Füge in derpackage.jsonfolgendes zum Abschnittscriptshinzu:"critical": "node ./src/critical.js" - Critical CSS generieren.
npm run critical
Konfiguration (critical.config.js)
Parameter für Konfigurationsdatei: critical.config.js
|
||||
|---|---|---|---|---|
| Parameter | Typ | Optional | Standard | Beschreibung |
baseUrl |
string |
Pflicht | 'http://127.0.0.1:8000' |
Die Basis-URL für die Anwendung. |
shopwarePages |
object |
Pflicht | s. unten | URLs für Shopware-Seiten (Kategorie- und Produktseiten). |
pages |
array |
Optional | s. unten | Seiten-Definitionen für die kritische CSS-Extraktion. |
Zusätzliche Informationen
|
||||
/**
* Critical CSS Konfiguration für Shopware 6 Theme
*
* Diese Konfiguration steuert die Extraktion von Critical CSS mit Penthouse.
* Critical CSS enthält nur die Styles, die für den Above-the-Fold Bereich
* einer Seite benötigt werden, um das initiale Rendering zu beschleunigen.
*
* Verwendung:
* - `cd custom/plugins/FietzRevplus/src/Resources/app/storefront`
* - `npm run critical` (führt die Extraktion aus)
*
* Generiert optimierte CSS-Dateien für verschiedene Seitentypen und Geräte.
* Output: ./dist/critical/*.css
*
* Anpassung für eigene Projekte:
* - baseUrl: Lokale/Staging URL der Shopware-Installation
* - categoryUrl/productUrl: Beliebige existierende SEO-URLs aus dem Shop
* - pages: Weitere Seitentypen hinzufügen oder Viewport-Größen anpassen
*/
module.exports = {
baseUrl: 'http://127.0.0.1:8000',
/**
* Shopware Seiten-URLs für Kategorie- und Produktseiten. Diese URLs erset-
* zen die Platzhalter in der `pages`-Definition.
*
* @param categoryUrl Beliebige existierende Kategorie-URL
* @param productUrl Beliebige existierende Produkt-URL
*/
shopwarePages: {
categoryUrl: '/Shoes/',
productUrl: '/Aerodynamic-Bronze-Water-Whole/SW-019a0152f66d7131a5a1408e48eb997e',
},
/**
* CSS-Selektoren, die immer in das Critical CSS aufgenommen werden sollen.
*
* Nützlich für:
* - Layout-kritische Styles zur Vermeidung von CLS (Cumulative Layout Shift)
* - Container-Klassen, die das Layout definieren
* - Klassen, die dynamisch erst nach dem initialen Rendering erscheinen
*
* @see https://github.com/pocketjoso/penthouse?tab=readme-ov-file#options
*/
forceInclude: [
'.container',
'.cms-section.boxed',
],
pages: [
{
name: 'home-desktop',
url: '/',
width: 1920,
height: 1080
},
{
name: 'home-mobile',
url: '/',
width: 375,
height: 667
},
{
name: 'category-desktop',
url: '/category-placeholder',
width: 1920,
height: 1080
},
{
name: 'category-mobile',
url: '/category-placeholder',
width: 375,
height: 667
},
{
name: 'product-desktop',
url: '/product-placeholder',
width: 1920,
height: 1080
},
{
name: 'product-mobile',
url: '/product-placeholder',
width: 375,
height: 667
}
],
penthouseOptions: {
timeout: 30000,
keepLargerMediaQueries: false,
propertiesToRemove: [
'transition', 'animation', 'transform', 'cursor'
]
},
outputDir: './dist/critical',
};
Ausführung (critical.js)
const penthouse = require('penthouse');
const fs = require('fs');
const path = require('path');
const config = require('../critical.config.js');
/**
* Critical CSS Extraction für Shopware 6 Theme
*
* Dieses Script extrahiert kritisches CSS mit Penthouse für verschiedene
* Seitentypen und Viewport-Größen, um die initiale Ladezeit zu optimieren.
*/
/**
* Sucht dynamisch nach der kompilierten all.css Datei im Shopware Theme-
* Verzeichnis.
*
* @returns {string|null} Pfad zur CSS-Datei oder null wenn nicht gefunden
*/
function findShopwareCss() {
// Suche nach der all.css Datei im public/theme Verzeichnis
const shopwareRoot = path.resolve(__dirname, '../../../../../../../../');
const publicPath = path.join(shopwareRoot, 'public', 'theme');
console.log('\nSuche CSS in:', publicPath);
if (!fs.existsSync(publicPath)) {
console.error('❌ Public theme Verzeichnis nicht gefunden:', publicPath);
return null;
}
const themeDirs = fs.readdirSync(publicPath);
for (const themeDir of themeDirs) {
const cssPath = path.join(publicPath, themeDir, 'css', 'all.css');
if (fs.existsSync(cssPath)) {
console.log(`✅ CSS-Datei gefunden: ${themeDir}/css/all.css`);
return cssPath;
}
}
return null;
}
/**
* Prüft, ob die CSS-Datei existiert und gibt den Pfad zurück.
* Beendet das Script mit Fehlermeldung falls keine CSS-Datei gefunden wurde.
*
* @returns {string} Pfad zur CSS-Datei
*/
function checkCssFile() {
const cssPath = findShopwareCss();
if (!cssPath) {
console.error(`❌ Keine CSS-Datei gefunden`);
console.log('💡 Führen Sie zuerst den Theme-Build aus');
process.exit(1);
}
return cssPath;
}
/**
* Stellt sicher, dass das Output-Verzeichnis für Critical CSS existiert.
* Erstellt das Verzeichnis rekursiv falls es nicht vorhanden ist.
*/
function ensureOutputDir() {
if (!fs.existsSync(config.outputDir)) {
fs.mkdirSync(config.outputDir, { recursive: true });
}
}
/**
* Liest den Inhalt der CSS-Datei.
*
* @param {string} cssPath - Pfad zur CSS-Datei
* @returns {string} CSS-Inhalt als String
*/
function getCssContent(cssPath) {
return fs.readFileSync(cssPath, 'utf8');
}
/**
* Ersetzt Platzhalter-URLs durch echte Shopware-URLs aus der Config.
*
* @param {string} url - URL mit möglichen Platzhaltern
* @returns {string} Aufgelöste URL
*/
function resolveUrl(url) {
return url
.replace('/category-placeholder', config.shopwarePages.categoryUrl)
.replace('/product-placeholder', config.shopwarePages.productUrl);
}
/**
* Extrahiert Critical CSS für eine spezifische Seite.
*
* @param {Object} pageConfig - Konfiguration der Seite (name, url, width, height)
* @param {string} cssContent - Vollständiger CSS-Inhalt
* @returns {Promise<Object|null>} Ergebnis-Objekt mit name, path und size oder null bei Fehler
*/
async function extractCriticalForPage(pageConfig, cssContent) {
const resolvedUrl = resolveUrl(pageConfig.url);
const fullUrl = `${config.baseUrl}${resolvedUrl}`;
console.log(`🔍 Extrahiere: ${pageConfig.name} (${pageConfig.width}x${pageConfig.height})`);
try {
const criticalCss = await penthouse({
url: fullUrl,
cssString: cssContent,
width: pageConfig.width,
height: pageConfig.height,
forceInclude: config.forceInclude || [],
...config.penthouseOptions
});
const outputPath = path.join(config.outputDir, `${pageConfig.name}.css`);
fs.writeFileSync(outputPath, criticalCss);
console.log(` ✅ ${(criticalCss.length / 1024).toFixed(2)} KB\n`);
return { name: pageConfig.name, path: outputPath, size: criticalCss.length };
} catch (error) {
console.error(` ❌ Fehler: ${error.message}\n`);
return null;
}
}
/**
* Erstellt eine kombinierte CSS-Datei aus allen extrahierten Critical CSS
* Dateien.
*
* @param {Array<Object>} results - Array mit Ergebnis-Objekten der Extraktion
*/
function createCombinedCss(results) {
if (results.length === 0) return;
const combinedCss = results.map(result =>
fs.readFileSync(result.path, 'utf8')
).join('\n\n');
const combinedPath = path.join(config.outputDir, 'combined.css');
fs.writeFileSync(combinedPath, combinedCss);
console.log(`✅ Kombinierte Datei: ${(combinedCss.length / 1024).toFixed(2)} KB`);
}
/**
* Hauptfunktion: Orchestriert die gesamte Critical CSS Extraktion.
*
* - Prüft CSS-Datei Existenz
* - Erstellt Output-Verzeichnis
* - Extrahiert Critical CSS für alle konfigurierten Seiten
* - Erstellt kombinierte Datei
*/
async function generateCriticalCSS() {
console.log('🚀 Starte Critical CSS Extraktion...');
const cssPath = checkCssFile();
ensureOutputDir();
const cssContent = getCssContent(cssPath);
console.log(`📄 CSS-Größe: ${(cssContent.length / 1024).toFixed(2)} KB\n`);
const results = [];
for (const pageConfig of config.pages) {
const result = await extractCriticalForPage(pageConfig, cssContent);
if (result) results.push(result);
// Kurze Pause zwischen Requests
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log(`📊 ${results.length} von ${config.pages.length} Dateien erstellt`);
createCombinedCss(results);
console.log('\n🎉 Critical CSS Extraktion abgeschlossen!');
}
// Startet den Extraktionsprozess
generateCriticalCSS().catch(error => {
console.error('💥 Fehler:', error.message);
process.exit(1);
});
Anpassung im Storefront-Template (Child-Theme)
Erweitere den Block layout_head_stylesheet der meta.html.twig-Datei des Child-Themes wie folgt, um das kritische CSS einzubinden:
{# FietzRevplusChild: Replace parent's critical CSS implementation with project specific styles. #}
{% block fietz_critical_css_include %}
{# Change the filename here, if it differs from parent. #}
{# % set criticalCssFile = pageType ~ '-' ~ deviceType ~ '.css' % #}
{% set criticalCss = source('@FietzRevplusChild/app/storefront/dist/critical/' ~ criticalCssFile, ignore_missing = true) %}
{# Fallback 1: Device only (home-mobile.css) #}
{% if not criticalCss %}
{% set fallbackFile = 'home-' ~ deviceType ~ '.css' %}
{% set criticalCss = source('@FietzRevplusChild/app/storefront/dist/critical/' ~ fallbackFile, ignore_missing = true) %}
{% endif %}
{# Fallback 2: Combined file #}
{% if not criticalCss %}
{% set criticalCss = source('@FietzRevplusChild/app/storefront/dist/critical/combined.css', ignore_missing = true) %}
{% endif %}
{% if criticalCss %}
<style>
/* Critical CSS: {{ criticalCssFile }} ({{ deviceType }}, {{ pageType }}) */
{{ criticalCss|raw }}
</style>
{% endif %}
{% endblock %}
Abhängigkeiten und eigene Individualisierung
Bitte beachte, dass die hier gezeigte Implementierung den Einsatz des RevPlus Themes ab Version v3.6.2 voraussetzt.
Immo W. Fietz
eCommerce Berater seit 24 Jahren
(0 51 41) 204 892 0¹
support@fietz-medien.de
Sie haben noch mehr Fragen? Wir sind auch auf WhatsApp erreichbar!
Sie haben noch mehr Fragen? Wir sind auch auf WhatsApp erreichbar!