- Wpięcie minifiera do szablonu
- Minifikacja CSS – pierwszy krok 🎯
- Zbieranie i minifikacja plików CSS 🧩
- Sprawdzanie aktualności cache ⏱️
- Budowanie zawartości CSS 🧱
- Minifikacja i zapis do cache 🗜️
- Teraz czas na pliki JavaScript ⚡
- Dlaczego JShrink? ✅
- Funkcja: sklejanie + cache + minifikacja JS (krok po kroku) 🧩
- Kompletny kod funkcji ✅
- Funkcja spinająca cały proces 🧩
- Kompletny kod pliku minifier.php
- Podsumowanie 🧠
WordPress daje nam dziesiątki gotowych wtyczek do minifikacji i cache’owania zasobów. Problem w tym, że w praktyce często robią za dużo, konfliktują się z motywem, wczytują własne skrypty albo psują kolejność ładowania plików.
Tworząc stronę WWW z własnym dedykowanym szablonem warto zadbać o pełną kontrolę nad CSS i JS.
W tym artykule pokażę krok po kroku, jak napisać własny, prosty i skuteczny system minifikacji z cache, bez wtyczek, bez kombinowania i bez zgadywania, co WordPress zrobi w tle. Wszystko osadzimy bezpośrednio w szablonie, dzięki czemu:
-
mamy pełną kontrolę nad kolejnością plików,
-
sami decydujemy, co i kiedy jest minifikowane,
-
cache działa dokładnie tak, jak tego oczekujemy,
-
a cały mechanizm jest lekki, przewidywalny i łatwy do debugowania.
Wpięcie minifiera do szablonu
Cały system minifikacji trzymamy poza functions.php, w osobnym katalogu. Dzięki temu:
-
logika minifikacji nie miesza się z resztą motywu,
-
łatwo ją rozwijać, testować i wyłączać,
-
zachowujemy porządek w strukturze projektu.
W functions.php dodajemy tylko jedno odwołanie:
require get_template_directory() . '/inc/minifier.php';
Katalog inc/ przeznaczamy na kod techniczny, a sam minifier będziemy dalej rozbijać na kolejne pliki, tak aby każda część (cache, CSS, JS) miała swoje miejsce.
Cała logika optymalizacji będzie zamknięta w jednym obszarze szablonu — bez chaosu i bez przypadkowych zależności.
Minifikacja CSS – pierwszy krok 🎯
Zaczynamy od najprostszego i w pełni kontrolowanego etapu, czyli minifikacji CSS. Nie korzystamy z zewnętrznych bibliotek ani parserów — to celowy wybór. Ten system ma być:
-
szybki,
-
przewidywalny,
-
łatwy do debugowania.
W pliku inc/minifier.php dodajemy funkcję odpowiedzialną wyłącznie za oczyszczanie CSS-a z nadmiarowych znaków:
function dcSMinifyCss(string $css){
// Usuwamy komentarze CSS /* ... */
$css = preg_replace('!/\*.*?\*/!s', '', $css);
// Redukujemy wielokrotne puste linie
$css = preg_replace('/\n\s*\n/', "\n", $css);
// Usuwamy znaki nowej linii, tabulatory i CR
$css = str_replace(["\r", "\n", "\t"], '', $css);
// Redukujemy wielokrotne spacje do jednej
$css = preg_replace('/\s+/', ' ', $css);
// Usuwamy spacje wokół znaków składniowych
$css = preg_replace('/\s*([{};:,])\s*/', '$1', $css);
// Usuwamy zbędny średnik przed zamknięciem bloku
$css = preg_replace('/;}/', '}', $css);
return trim($css);
}
Dlaczego tak, a nie „pełna” minifikacja? 🧠
Ten mechanizm:
-
nie zmienia semantyki CSS,
-
nie próbuje „być mądrzejszy” od przeglądarki,
-
nie psuje niestandardowych hacków ani zmiennych CSS.
To jest bezpieczna minifikacja poziomu szablonu, idealna do:
-
własnych stylów,
-
kontrolowanych bibliotek,
-
plików generowanych przez nasz motyw.
Zbieranie i minifikacja plików CSS 🧩
Na tym etapie przechodzimy z „suchej” funkcji minifikującej do realnego mechanizmu budowania jednego pliku CSS z cache.
Całość zamykamy w jednej funkcji, która:
-
zbiera wszystkie style motywu,
-
sprawdza, czy cache jest aktualny,
-
w razie potrzeby regeneruje plik,
-
zwraca gotowy URL do podpięcia w
<link>.
function dc_get_minified_css()
{
$dc_assets_css = get_template_directory() . '/cache/';
$dc_file_css_name = 'dc-theme.min.css';
$cache_file = $dc_assets_css . $dc_file_css_name;
// Lista CSS (lokalne + CDN)
$files_css = array(
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css',
get_template_directory() . '/style.css',
get_template_directory() . '/assets/css/header.css',
get_template_directory() . '/assets/css/header_baner.css',
get_template_directory() . '/assets/css/header_contact.css',
get_template_directory() . '/assets/css/header_menu.css',
get_template_directory() . '/assets/css/header_menu_mobile.css',
get_template_directory() . '/assets/css/header_search.css',
get_template_directory() . '/assets/css/header_slidecart.css',
get_template_directory() . '/assets/css/header_top.css',
get_template_directory() . '/assets/css/owl.carousel.min.css',
get_template_directory() . '/assets/css/products_carousel.css',
get_template_directory() . '/assets/fontawesome/css/all.min.css',
get_template_directory() . '/assets/css/forms.css',
get_template_directory() . '/assets/css/footer.css',
get_template_directory() . '/assets/css/product_card.css',
get_template_directory() . '/assets/css/home.css',
get_template_directory() . '/assets/css/woocommerce.css'
);
$regenerate = true;
Lista plików = pełna kontrola 🎛️
Nie polegamy na wp_enqueue_style.
Kolejność plików jest jawna i przewidywalna, co ma kluczowe znaczenie przy:
-
nadpisywaniu styli,
-
Bootstrapie,
-
WooCommerce,
-
komponentach UI.
CDN i pliki lokalne są obsługiwane razem, bez wyjątków.
Sprawdzanie aktualności cache ⏱️
if ( file_exists( $cache_file ) ) {
$cache_mtime = filemtime( $cache_file );
$regenerate = false;
foreach ( $files_css as $file ) {
if ( preg_match('#^https?://#', $file) ) {
continue;
}
if ( file_exists($file) && filemtime($file) > $cache_mtime ) {
$regenerate = true;
break;
}
}
}
Cache jest regenerowany tylko wtedy, gdy:
-
którykolwiek lokalny plik CSS został zmodyfikowany,
-
albo cache jeszcze nie istnieje.
Pliki z CDN celowo pomijamy przy sprawdzaniu czasu modyfikacji — traktujemy je jako stabilne zależności.
Efekt:
-
brak zbędnej minifikacji przy każdym requestcie 🚀
-
brak „zgadywania”, czy coś się zmieniło.
Budowanie zawartości CSS 🧱
if ( function_exists('dcSMinifyCss') ) {
$content = dcSMinifyCss($content);
}
@file_put_contents( $cache_file, $content );
}
return get_template_directory_uri() . '/cache/' . $dc_file_css_name;
}
Komentarze typu /* FILE */ i /* CDN */ są świadome:
-
ułatwiają debug,
-
pozwalają szybko zlokalizować problematyczny styl,
-
znikną później w procesie minifikacji.
Minifikacja i zapis do cache 🗜️
if ( function_exists('dcSMinifyCss') ) {
$content = dcSMinifyCss($content);
}
@file_put_contents( $cache_file, $content );
}
return get_template_directory_uri() . '/cache/' . $dc_file_css_name;
}
Minifikacja:
-
działa tylko na końcowym stringu,
-
nie dotyka pojedynczych plików,
-
jest szybka i bezpieczna.
Funkcja zwraca gotowy URL do pliku cache — bez warunków, bez logiki po stronie frontendu.
Teraz czas na pliki JavaScript ⚡
CSS mogliśmy minifikować prostymi regexami, bo CSS jest dość „wybaczający” i ma przewidywalną składnię (spacje, komentarze, średniki).
Z JavaScriptem tak się nie da.
Dlaczego nasza minifikacja CSS nie nadaje się do JS? 🚫
Minifikator CSS usuwa komentarze i spacje na ślepo. W JS to może rozwalić kod w kilka klasycznych miejsc:
-
// komentarzw JS kończy się końcem linii — jeśli wywalimy znaki nowej linii „hurtowo”, możemy skleić dwie instrukcje w jedną. -
RegExp w JS (np.
/\/*.../) potrafi wyglądać jak komentarz/* ... */i regex minifikatora CSS może to błędnie wyciąć. -
Spacje w JS czasem mają znaczenie semantyczne (np.
return+ nowa linia → pułapka ASI). -
Łatwo też przypadkiem skleić tokeny w coś innego (
a + +b,a - -b, itp.).
Dlatego do JS potrzebujemy narzędzia, które parsuje składnię, a nie „tnij-stringi”.
Dlaczego JShrink? ✅
JShrink to lekka biblioteka PHP, która minifikuje JavaScript w sposób bezpieczniejszy niż regexy.
Pobieramy z repo:https://github.com/tedious/JShrink
Z repozytorium bierzemy plik:
-
Minifier.php
i wrzucamy do szablonu tutaj:
-
wp-content/themes/TWOJ_MOTYW/inc/JShrink/Minifier.php
Funkcja: sklejanie + cache + minifikacja JS (krok po kroku) 🧩
Poniższa funkcja buduje jeden plik dc-theme.min.js w katalogu cache/ i regeneruje go tylko wtedy, kiedy to ma sens.
1) Ustalamy ścieżkę cache i nazwę pliku
-
cache trzymamy w
.../theme/cache/ -
wynik to zawsze
dc-theme.min.js
2) Definiujemy listę plików JS (kolejność ma znaczenie)
To Ty decydujesz, co wchodzi do paczki i w jakiej kolejności. Najpierw CDN (Bootstrap), potem pliki motywu.
3) Sprawdzamy, czy cache jest aktualny
Jeśli cache istnieje:
-
bierzemy jego
filemtime -
porównujemy z czasami modyfikacji lokalnych plików JS
-
CDN pomijamy (nie mamy
filemtimei traktujemy je jako zewnętrzne zależności)
Jeśli którykolwiek lokalny plik jest nowszy → przebudowujemy cache.
4) Sklejamy pliki w jeden string
Dla debugowania dodajesz nagłówki /* CDN: ... */ i /* FILE: ... */.
Potem i tak minifikacja to spłaszczy.
5) Ładujemy JShrink i minifikujemy całość
require_once wrzucasz wewnątrz regeneracji, dzięki czemu biblioteka ładuje się tylko wtedy, kiedy faktycznie przebudowujesz paczkę. 👍
6) Zapisujemy wynik do pliku i zwracamy URL
Funkcja na końcu zwraca gotowy URL do podpięcia w HTML.
Kompletny kod funkcji ✅
function dc_get_minified_js(){
$dc_assets_js = get_template_directory() . '/cache/';
$dc_file_js_name = 'dc-theme.min.js';
$cache_file = $dc_assets_js . $dc_file_js_name;
// Lista JS (lokalne + CDN)
$files_js = array(
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js',
get_template_directory() . '/assets/js/dc-slidecart.js',
get_template_directory() . '/assets/js/woocommerce.js',
get_template_directory() . '/assets/js/dc-theme.js',
get_template_directory() . '/assets/js/custom.js'
);
$regenerate = true;
if ( file_exists( $cache_file ) ) {
$cache_mtime = filemtime( $cache_file );
$regenerate = false;
foreach ( $files_js as $file ) {
if ( preg_match('#^https?://#', $file) ) {
continue;
}
if ( file_exists($file) && filemtime($file) > $cache_mtime ) {
$regenerate = true;
break;
}
}
}
if ( $regenerate ) {
$content = '';
foreach ( $files_js as $file ) {
if ( preg_match('#^https?://#', $file) ) {
$js = @file_get_contents($file);
if ( $js !== false ) {
$content .= "\n/* CDN: $file */\n" . $js;
}
} elseif ( file_exists($file) ) {
$js = @file_get_contents($file);
if ( $js !== false ) {
$content .= "\n/* FILE: $file */\n" . $js;
}
}
}
// JShrink: pobrany z https://github.com/tedious/JShrink
// Plik: /inc/JShrink/Minifier.php
require_once(get_template_directory() . '/inc/JShrink/Minifier.php');
// Minifikacja przez JShrink
if ( class_exists('\JShrink\Minifier') ) {
try {
$content = \JShrink\Minifier::minify($content, [
'flaggedComments' => false
]);
} catch (Exception $e) {
// W razie błędu zapisujemy nieminifikowany JS
}
}
@file_put_contents( $cache_file, $content );
}
return get_template_directory_uri() . '/cache/' . $dc_file_js_name;
}
Funkcja spinająca cały proces 🧩
To jest funkcja, która zamyka cały system. WordPress dostaje tylko jeden CSS i jeden JS — dokładnie te, które wcześniej wygenerowaliśmy.
function designcart_enqueue_assets(){
wp_enqueue_style(
'dc-theme-min-css',
dc_get_minified_css(),
[],
filemtime( get_template_directory() . '/cache/dc-theme.min.css' )
);
wp_enqueue_script(
'dc-theme-min-js',
dc_get_minified_js(),
[],
filemtime( get_template_directory() . '/cache/dc-theme.min.js' )
);
}
add_action( 'wp_enqueue_scripts', 'designcart_enqueue_assets' );
Co tu się faktycznie dzieje? 🧠
1) WordPress dostaje jeden plik CSS i jeden JS
Nie rejestrujemy dziesięciu styli i pięciu skryptów.
Dla frontendu istnieją tylko:
-
dc-theme-min.css -
dc-theme-min.js
Mniej requestów = szybsze ładowanie 🚀
2) Funkcje dc_get_minified_css() i dc_get_minified_js()
Podczas enqueue:
-
jeśli cache jest aktualny → nic się nie przebudowuje,
-
jeśli coś się zmieniło → pliki są regenerowane przed załadowaniem.
WordPress nie wie, że w tle dzieje się minifikacja i cache — dostaje gotowy URL i koniec.
3) Wersjonowanie przez filemtime() 🔁
To bardzo ważny detal.
filemtime( get_template_directory() . '/cache/dc-theme.min.css' )
oraz:
filemtime( get_template_directory() . '/cache/dc-theme.min.js' )
Dzięki temu:
-
po każdej regeneracji cache zmienia się wersja pliku,
-
przeglądarka automatycznie pobiera nową wersję,
-
nie ma problemu z „wiszącym” starym CSS lub JS.
Zero ręcznego czyszczenia cache w przeglądarce 👌
Efekt końcowy 🎯
Po stronie WordPressa:
-
standardowe API (
wp_enqueue_*) -
zero hacków
-
pełna zgodność z core
Po stronie frontendu:
-
jeden CSS
-
jeden JS
-
cache na dysku
-
wersjonowanie automatyczne
Masz własny system minifikacji i cache w szablonie WordPress, bez wtyczek, bez konfliktów i bez zgadywania, co dzieje się „magicznie” w tle.
Kompletny kod pliku minifier.php
<?php
function dcSMinifyCss(string $css){
$css = preg_replace('!/\*.*?\*/!s', '', $css);
$css = preg_replace('/\n\s*\n/', "\n", $css);
$css = str_replace(["\r", "\n", "\t"], '', $css);
$css = preg_replace('/\s+/', ' ', $css);
$css = preg_replace('/\s*([{};:,])\s*/', '$1', $css);
$css = preg_replace('/;}/', '}', $css);
return trim($css);
}
function dc_get_minified_css() {
$dc_assets_css = get_template_directory() . '/cache/';
$dc_file_css_name = 'dc-theme.min.css';
$cache_file = $dc_assets_css.$dc_file_css_name;
// Lista CSS
$files_css = array(
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css',
get_template_directory() . '/style.css',
get_template_directory() . '/assets/css/header.css',
get_template_directory() . '/assets/css/header_baner.css',
get_template_directory() . '/assets/css/header_contact.css',
get_template_directory() . '/assets/css/header_menu.css',
get_template_directory() . '/assets/css/header_menu_mobile.css',
get_template_directory() . '/assets/css/header_search.css',
get_template_directory() . '/assets/css/header_slidecart.css',
get_template_directory() . '/assets/css/header_top.css',
get_template_directory() . '/assets/css/owl.carousel.min.css',
get_template_directory() . '/assets/css/products_carousel.css',
get_template_directory() . '/assets/fontawesome/css/all.min.css',
get_template_directory() . '/assets/css/forms.css',
get_template_directory() . '/assets/css/footer.css',
get_template_directory() . '/assets/css/product_card.css',
get_template_directory() . '/assets/css/home.css',
get_template_directory() . '/assets/css/woocommerce.css'
);
$regenerate = true;
if ( file_exists( $cache_file ) ) {
$cache_mtime = filemtime( $cache_file );
$regenerate = false;
foreach ( $files_css as $file ) {
if ( preg_match('#^https?://#', $file) ) {
continue;
}
if (file_exists($file) && filemtime($file) > $cache_mtime ) {
$regenerate = true;
break;
}
}
}
if ( $regenerate ) {
$content = '';
foreach ( $files_css as $file ) {
if ( preg_match('#^https?://#', $file) ) {
$css = @file_get_contents($file);
if ( $css !== false ) {
$content .= "\n/* CDN: $file */\n" . $css;
}
} elseif ( file_exists($file) ) {
$css = @file_get_contents($file);
if ( $css !== false ) {
$content .= "\n/* FILE: $file */\n" . $css;
}
}
}
if ( function_exists('dcSMinifyCss') ) {
$content = dcSMinifyCss($content);
}
@file_put_contents( $cache_file, $content );
}
return get_template_directory_uri() . '/cache/' . $dc_file_css_name;
}
function dc_get_minified_js(){
$dc_assets_js = get_template_directory() . '/cache/';
$dc_file_js_name = 'dc-theme.min.js';
$cache_file = $dc_assets_js . $dc_file_js_name;
// Lista JS (lokalne + CDN)
$files_js = array(
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js',
get_template_directory() . '/assets/js/dc-slidecart.js',
get_template_directory() . '/assets/js/woocommerce.js',
get_template_directory() . '/assets/js/dc-theme.js',
get_template_directory() . '/assets/js/custom.js'
);
$regenerate = true;
if ( file_exists( $cache_file ) ) {
$cache_mtime = filemtime( $cache_file );
$regenerate = false;
foreach ( $files_js as $file ) {
if ( preg_match('#^https?://#', $file) ) {
continue;
}
if ( file_exists($file) && filemtime($file) > $cache_mtime ) {
$regenerate = true;
break;
}
}
}
if ( $regenerate ) {
$content = '';
foreach ( $files_js as $file ) {
if ( preg_match('#^https?://#', $file) ) {
$js = @file_get_contents($file);
if ( $js !== false ) {
$content .= "\n/* CDN: $file */\n" . $js;
}
} elseif ( file_exists($file) ) {
$js = @file_get_contents($file);
if ( $js !== false ) {
$content .= "\n/* FILE: $file */\n" . $js;
}
}
}
require_once(get_template_directory().'/inc/JShrink/Minifier.php');
// Minifikacja przez JShrink
if ( class_exists('\JShrink\Minifier') ) {
try {
$content = \JShrink\Minifier::minify($content, [
'flaggedComments' => false
]);
} catch (Exception $e) {
// W razie błędu zapisujemy nieminifikowany JS
}
}
@file_put_contents( $cache_file, $content );
}
return get_template_directory_uri() . '/cache/' . $dc_file_js_name;
}
function designcart_enqueue_assets() {
wp_enqueue_style(
'dc-theme-min-css',
dc_get_minified_css(),
[],
filemtime( get_template_directory() . '/cache/dc-theme.min.css' )
);
wp_enqueue_script(
'dc-theme-min-js',
dc_get_minified_js(),
[],
filemtime( get_template_directory() . '/cache/dc-theme.min.js' )
);
}
add_action( 'wp_enqueue_scripts', 'designcart_enqueue_assets' );
Podsumowanie 🧠
Zbudowaliśmy własny, w pełni kontrolowany system minifikacji i cache bez użycia wtyczek i bez ingerowania w core WordPressa. Cała logika działa na poziomie szablonu, dokładnie tam, gdzie powinna — blisko kodu, który faktycznie kontrolujemy.
Ten mechanizm:
-
łączy wszystkie style w jeden plik CSS,
-
łączy wszystkie skrypty w jeden plik JS,
-
minifikuje CSS bezpiecznie prostą logiką,
-
minifikuje JS przy użyciu JShrink, czyli parsera, a nie regexów,
-
zapisuje wynik do cache na dysku,
-
regeneruje pliki tylko wtedy, gdy coś się zmieni,
-
automatycznie wersjonuje zasoby przez
filemtime().
Efekt końcowy to:
-
mniej requestów HTTP,
-
szybsze ładowanie strony,
-
brak konfliktów z wtyczkami optymalizacyjnymi,
-
pełna kontrola nad kolejnością ładowania plików,
-
przewidywalne działanie w środowisku produkcyjnym.
To rozwiązanie ma największy sens w customowych motywach, gdzie wiemy, jakie pliki są ładowane i w jakiej kolejności. Nie próbuje być uniwersalne ani „magiczne” — robi dokładnie to, co ma robić, i nic więcej.
Zapoznaj się też z naszą ofertą tworzenia stron WWW nie tylko w Wordpress
Sklepy internetowe Woocommerce
Sklepy internetowe Opencart
Sklepy internetowe Prestashop
Sklepy internetowe Magento
Strony internetowe Joomla!
Strony Internetowe Wordpress


