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 %}
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!