Asset Management
Table of Contents
Asset Management
Modern Asset Loading & Optimization
Jankx 2.0 sử dụng hệ thống asset management hiện đại với lazy loading, optimization và selective loading.
🏗 Asset Architecture
Asset System Structure
┌─────────────────────────────────────┐
│ Asset Manager │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Asset │ │ Asset │ │
│ │ Registry │ │ Optimizer │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Asset Loading │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Lazy │ │ Selective │ │
│ │ Loading │ │ Loading │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Asset Optimization │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Minify │ │ Combine │ │
│ │ Assets │ │ Assets │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
🔧 Asset Manager
Asset Manager Implementation
<?php
namespace Jankx\Assets;
class AssetManager
{
private $assets = [];
private $optimizer;
private $lazyLoader;
private $blockAssets = [];
public function __construct(AssetOptimizer $optimizer, LazyAssetLoader $lazyLoader)
{
$this->optimizer = $optimizer;
$this->lazyLoader = $lazyLoader;
$this->registerDefaultAssets();
}
public function registerAsset(string $handle, array $config): void
{
$this->assets[$handle] = $config;
}
public function enqueueAsset(string $handle): void
{
if (!isset($this->assets[$handle])) {
return;
}
$asset = $this->assets[$handle];
if (isset($asset['script'])) {
wp_enqueue_script(
$handle,
$asset['script'],
$asset['dependencies'] ?? [],
$asset['version'] ?? \Jankx\Jankx::getFrameworkVersion(),
$asset['in_footer'] ?? true
);
}
if (isset($asset['style'])) {
wp_enqueue_style(
$handle,
$asset['style'],
$asset['dependencies'] ?? [],
$asset['version'] ?? \Jankx\Jankx::getFrameworkVersion()
);
}
}
public function enqueueFrontendAssets(): void
{
// Enqueue critical assets
$this->enqueueCriticalAssets();
// Enqueue main assets
$this->enqueueMainAssets();
// Enqueue block-specific assets
$this->enqueueBlockAssets();
// Enqueue lazy assets
$this->enqueueLazyAssets();
}
public function enqueueAdminAssets(): void
{
// Enqueue admin-specific assets
$this->enqueueAsset('jankx-admin');
$this->enqueueAsset('jankx-admin-style');
}
private function enqueueCriticalAssets(): void
{
// Inline critical CSS
$this->inlineCriticalCSS();
// Preload critical fonts
$this->preloadCriticalFonts();
// Preload critical images
$this->preloadCriticalImages();
}
private function enqueueMainAssets(): void
{
$this->enqueueAsset('jankx-main');
$this->enqueueAsset('jankx-style');
}
private function enqueueBlockAssets(): void
{
$usedBlocks = $this->getUsedBlocks();
foreach ($usedBlocks as $blockName) {
$this->enqueueBlockAsset($blockName);
}
}
private function enqueueLazyAssets(): void
{
$this->lazyLoader->enqueueLazyAssets();
}
private function registerDefaultAssets(): void
{
// Main assets
$this->registerAsset('jankx-main', [
'script' => get_theme_file_uri('assets/dist/js/main.min.js'),
'dependencies' => ['jquery'],
'version' => \Jankx\Jankx::getFrameworkVersion(),
'in_footer' => true,
]);
$this->registerAsset('jankx-style', [
'style' => get_theme_file_uri('assets/dist/css/main.min.css'),
'dependencies' => [],
'version' => \Jankx\Jankx::getFrameworkVersion(),
]);
// Admin assets
$this->registerAsset('jankx-admin', [
'script' => get_theme_file_uri('assets/dist/js/admin.min.js'),
'dependencies' => ['jquery', 'wp-color-picker'],
'version' => \Jankx\Jankx::getFrameworkVersion(),
'in_footer' => true,
]);
$this->registerAsset('jankx-admin-style', [
'style' => get_theme_file_uri('assets/dist/css/admin.min.css'),
'dependencies' => ['wp-color-picker'],
'version' => \Jankx\Jankx::getFrameworkVersion(),
]);
}
}
🚀 Asset Optimizer
Asset Optimizer Implementation
<?php
namespace Jankx\Assets;
class AssetOptimizer
{
private $config;
public function __construct(array $config = [])
{
$this->config = array_merge([
'minify_css' => true,
'minify_js' => true,
'combine_css' => true,
'combine_js' => true,
'optimize_images' => true,
'generate_webp' => true,
], $config);
}
public function optimizeCSS(string $css): string
{
if ($this->config['minify_css']) {
$css = $this->minifyCSS($css);
}
return $css;
}
public function optimizeJS(string $js): string
{
if ($this->config['minify_js']) {
$js = $this->minifyJS($js);
}
return $js;
}
public function combineCSS(array $files): string
{
if (!$this->config['combine_css']) {
return '';
}
$combined = '';
foreach ($files as $file) {
if (file_exists($file)) {
$combined .= file_get_contents($file) . "\n";
}
}
return $this->optimizeCSS($combined);
}
public function combineJS(array $files): string
{
if (!$this->config['combine_js']) {
return '';
}
$combined = '';
foreach ($files as $file) {
if (file_exists($file)) {
$combined .= file_get_contents($file) . ";\n";
}
}
return $this->optimizeJS($combined);
}
public function optimizeImage(string $imagePath): array
{
$optimized = [];
if ($this->config['optimize_images']) {
$optimized['original'] = $this->compressImage($imagePath);
}
if ($this->config['generate_webp']) {
$optimized['webp'] = $this->generateWebP($imagePath);
}
return $optimized;
}
private function minifyCSS(string $css): string
{
// Remove comments
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
// Remove whitespace
$css = preg_replace('/\s+/', ' ', $css);
// Remove unnecessary semicolons
$css = str_replace(';}', '}', $css);
return trim($css);
}
private function minifyJS(string $js): string
{
// Remove comments
$js = preg_replace('/\/\*.*?\*\//s', '', $js);
$js = preg_replace('/\/\/.*$/m', '', $js);
// Remove whitespace
$js = preg_replace('/\s+/', ' ', $js);
return trim($js);
}
private function compressImage(string $imagePath): string
{
$image = imagecreatefromstring(file_get_contents($imagePath));
if (!$image) {
return $imagePath;
}
$quality = 85;
$outputPath = str_replace(['.jpg', '.jpeg', '.png'], '_optimized.jpg', $imagePath);
imagejpeg($image, $outputPath, $quality);
imagedestroy($image);
return $outputPath;
}
private function generateWebP(string $imagePath): string
{
$image = imagecreatefromstring(file_get_contents($imagePath));
if (!$image) {
return $imagePath;
}
$outputPath = str_replace(['.jpg', '.jpeg', '.png'], '.webp', $imagePath);
imagewebp($image, $outputPath, 85);
imagedestroy($image);
return $outputPath;
}
}
🔄 Lazy Asset Loader
Lazy Asset Loader Implementation
<?php
namespace Jankx\Assets;
class LazyAssetLoader
{
private $lazyAssets = [];
private $loadedAssets = [];
public function __construct()
{
$this->registerLazyAssets();
}
public function registerLazyAsset(string $name, array $config): void
{
$this->lazyAssets[$name] = $config;
}
public function enqueueLazyAssets(): void
{
add_action('wp_footer', [$this, 'addLazyAssetScripts']);
}
public function addLazyAssetScripts(): void
{
?>
<script>
// Lazy asset loader
class JankxLazyAssetLoader {
constructor() {
this.loadedAssets = new Set();
this.init();
}
init() {
this.observeIntersection();
this.bindEvents();
}
observeIntersection() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadAssetsForElement(entry.target);
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '50px',
threshold: 0.1
});
// Observe elements that need lazy assets
document.querySelectorAll('[data-lazy-assets]').forEach(element => {
observer.observe(element);
});
}
bindEvents() {
// Load assets on user interaction
document.addEventListener('click', (e) => {
if (e.target.matches('[data-lazy-assets]')) {
this.loadAssetsForElement(e.target);
}
});
// Load assets on scroll
let scrollTimeout;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
this.loadVisibleAssets();
}, 100);
});
}
loadAssetsForElement(element) {
const assetNames = element.dataset.lazyAssets?.split(',') || [];
assetNames.forEach(assetName => {
if (!this.loadedAssets.has(assetName)) {
this.loadAsset(assetName);
this.loadedAssets.add(assetName);
}
});
}
loadVisibleAssets() {
const visibleElements = document.querySelectorAll('[data-lazy-assets]:not([data-assets-loaded])');
visibleElements.forEach(element => {
const rect = element.getBoundingClientRect();
const isVisible = rect.top < window.innerHeight && rect.bottom > 0;
if (isVisible) {
this.loadAssetsForElement(element);
element.setAttribute('data-assets-loaded', 'true');
}
});
}
loadAsset(assetName) {
const assetConfig = <?php echo json_encode($this->lazyAssets); ?>[assetName];
if (!assetConfig) {
return;
}
if (assetConfig.css) {
this.loadCSS(assetConfig.css);
}
if (assetConfig.js) {
this.loadJS(assetConfig.js);
}
}
loadCSS(href) {
if (document.querySelector(`link[href="${href}"]`)) {
return;
}
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}
loadJS(src) {
if (document.querySelector(`script[src="${src}"]`)) {
return;
}
const script = document.createElement('script');
script.src = src;
script.async = true;
document.head.appendChild(script);
}
}
// Initialize lazy asset loader
const jankxLazyLoader = new JankxLazyAssetLoader();
</script>
<?php
}
private function registerLazyAssets(): void
{
// Register lazy assets
$this->registerLazyAsset('lightbox', [
'css' => get_theme_file_uri('assets/dist/css/lightbox.min.css'),
'js' => get_theme_file_uri('assets/dist/js/lightbox.min.js'),
]);
$this->registerLazyAsset('slider', [
'css' => get_theme_file_uri('assets/dist/css/slider.min.css'),
'js' => get_theme_file_uri('assets/dist/js/slider.min.js'),
]);
$this->registerLazyAsset('modal', [
'css' => get_theme_file_uri('assets/dist/css/modal.min.css'),
'js' => get_theme_file_uri('assets/dist/js/modal.min.js'),
]);
$this->registerLazyAsset('form-validation', [
'css' => get_theme_file_uri('assets/dist/css/form-validation.min.css'),
'js' => get_theme_file_uri('assets/dist/js/form-validation.min.js'),
]);
}
}
🎯 Critical CSS Generator
Critical CSS Generator Implementation
<?php
namespace Jankx\Assets;
class CriticalCSSGenerator
{
private $criticalSelectors = [];
private $criticalCSS = '';
public function __construct()
{
$this->setCriticalSelectors();
}
public function generateCriticalCSS(): string
{
$this->extractCriticalCSS();
return $this->optimizeCriticalCSS();
}
public function inlineCriticalCSS(): void
{
$criticalCSS = $this->generateCriticalCSS();
add_action('wp_head', function() use ($criticalCSS) {
echo '<style id="jankx-critical-css">' . $criticalCSS . '</style>';
});
}
private function setCriticalSelectors(): void
{
$this->criticalSelectors = [
// Layout
'.jankx-layout',
'.jankx-layout-header',
'.jankx-layout-main',
'.jankx-layout-footer',
// Navigation
'.jankx-navigation',
'.jankx-navigation-menu',
'.jankx-navigation-toggle',
// Typography
'body',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'p', 'a', 'strong', 'em',
// Buttons
'.jankx-button',
'.jankx-button-primary',
'.jankx-button-secondary',
// Forms
'.jankx-form',
'.jankx-input',
'.jankx-textarea',
'.jankx-select',
// Hero section
'.jankx-hero',
'.jankx-hero-title',
'.jankx-hero-description',
// Blocks
'.jankx-block',
'.jankx-block-testimonial',
'.jankx-block-hero',
];
}
private function extractCriticalCSS(): void
{
$cssFiles = [
get_template_directory() . '/assets/css/main.css',
get_template_directory() . '/assets/css/layouts.css',
get_template_directory() . '/assets/css/components.css',
];
$allCSS = '';
foreach ($cssFiles as $file) {
if (file_exists($file)) {
$allCSS .= file_get_contents($file) . "\n";
}
}
$this->criticalCSS = $this->extractCSSForSelectors($allCSS, $this->criticalSelectors);
}
private function extractCSSForSelectors(string $css, array $selectors): string
{
$criticalCSS = '';
// Split CSS into rules
$rules = $this->parseCSSRules($css);
foreach ($rules as $rule) {
if ($this->isCriticalRule($rule, $selectors)) {
$criticalCSS .= $rule . "\n";
}
}
return $criticalCSS;
}
private function parseCSSRules(string $css): array
{
// Basic CSS parser
$rules = [];
$currentRule = '';
$braceCount = 0;
for ($i = 0; $i < strlen($css); $i++) {
$char = $css[$i];
$currentRule .= $char;
if ($char === '{') {
$braceCount++;
} elseif ($char === '}') {
$braceCount--;
if ($braceCount === 0) {
$rules[] = trim($currentRule);
$currentRule = '';
}
}
}
return $rules;
}
private function isCriticalRule(string $rule, array $selectors): bool
{
foreach ($selectors as $selector) {
if (strpos($rule, $selector) !== false) {
return true;
}
}
return false;
}
private function optimizeCriticalCSS(): string
{
// Remove comments
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $this->criticalCSS);
// Remove whitespace
$css = preg_replace('/\s+/', ' ', $css);
// Remove unnecessary semicolons
$css = str_replace(';}', '}', $css);
return trim($css);
}
}
📊 Asset Performance Monitor
Asset Performance Monitor Implementation
<?php
namespace Jankx\Assets;
class AssetPerformanceMonitor
{
private $metrics = [];
public function trackAssetLoad(string $assetName, float $loadTime): void
{
$this->metrics[$assetName] = [
'load_time' => $loadTime,
'timestamp' => microtime(true),
];
}
public function getAssetMetrics(): array
{
return $this->metrics;
}
public function getAverageLoadTime(): float
{
if (empty($this->metrics)) {
return 0;
}
$totalTime = array_sum(array_column($this->metrics, 'load_time'));
return $totalTime / count($this->metrics);
}
public function getSlowestAssets(int $limit = 5): array
{
uasort($this->metrics, function($a, $b) {
return $b['load_time'] <=> $a['load_time'];
});
return array_slice($this->metrics, 0, $limit, true);
}
public function getMetrics(): array
{
return [
'total_assets' => count($this->metrics),
'average_load_time' => $this->getAverageLoadTime(),
'slowest_assets' => $this->getSlowestAssets()
];
}
}
Next: Caching Strategy | Lazy Loading |