From e389093a0a3dfecc2b9f1f73aba618b4d29a6275 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 14 Jan 2026 23:25:10 +0000 Subject: [PATCH] Deduplicate CDN assets by URL in bundle compiler Unify CDN asset collection between dev and prod modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Commands/Rsx/Prod_Build_Command.php | 14 +-- app/RSpade/Core/Bundle/BundleCompiler.php | 109 ++++-------------- 2 files changed, 26 insertions(+), 97 deletions(-) diff --git a/app/RSpade/Commands/Rsx/Prod_Build_Command.php b/app/RSpade/Commands/Rsx/Prod_Build_Command.php index a21ed4140..b2933cda4 100755 --- a/app/RSpade/Commands/Rsx/Prod_Build_Command.php +++ b/app/RSpade/Commands/Rsx/Prod_Build_Command.php @@ -132,19 +132,9 @@ class Prod_Build_Command extends Command if (!$this->option('skip-laravel-cache')) { $this->line('[3/3] Building Laravel caches...'); - passthru('php artisan config:cache 2>/dev/null', $exit_code); + passthru('php artisan optimize:cache 2>/dev/null', $exit_code); if ($exit_code === 0) { - $this->line(' Config cached'); - } - - passthru('php artisan route:cache 2>/dev/null', $exit_code); - if ($exit_code === 0) { - $this->line(' Routes cached'); - } - - passthru('php artisan view:cache 2>/dev/null', $exit_code); - if ($exit_code === 0) { - $this->line(' Views cached'); + $this->line(' Laravel caches built'); } } else { $this->line('[3/3] Skipping Laravel caches (--skip-laravel-cache)'); diff --git a/app/RSpade/Core/Bundle/BundleCompiler.php b/app/RSpade/Core/Bundle/BundleCompiler.php index 9ba4ded32..2a85e5b53 100644 --- a/app/RSpade/Core/Bundle/BundleCompiler.php +++ b/app/RSpade/Core/Bundle/BundleCompiler.php @@ -463,9 +463,14 @@ class BundleCompiler 'app_css_bundle_path' => !empty($app_css_files) ? basename($app_css_files[0]) : null, ]; - // Also resolve CDN assets - they're served separately via /_vendor/ URLs - // We need to resolve the bundle includes to get CDN asset definitions - $this->_resolve_cdn_assets_only(); + // Resolve CDN assets using the same code path as dev mode + // This ensures directory scanning, nested bundles, etc. all work the same + // File collection happens but we ignore it since we use cached bundles + $bundle_fqcn = $this->_get_bundle_fqcn($this->bundle_name); + $this->resolved_includes[$bundle_fqcn] = true; + $this->root_bundle_class = $bundle_fqcn; + $this->_process_required_bundles(); + $this->_resolve_bundle($bundle_fqcn); if (!empty($this->cdn_assets['js'])) { $result['cdn_js'] = $this->_prepare_cdn_assets($this->cdn_assets['js'], 'js'); @@ -479,81 +484,6 @@ class BundleCompiler return null; } - - /** - * Resolve only CDN assets without full bundle compilation - * - * Used when serving from production cache to get CDN asset URLs - * without re-resolving and re-compiling all bundle files. - */ - protected function _resolve_cdn_assets_only(): void - { - // Process required bundles first (they may have CDN assets) - $required_bundles = config('rsx.required_bundles', []); - $bundle_aliases = config('rsx.bundle_aliases', []); - - foreach ($required_bundles as $alias) { - if (isset($bundle_aliases[$alias])) { - $this->_collect_cdn_assets_from_include($bundle_aliases[$alias]); - } - } - - // Get the bundle's own CDN assets - $fqcn = $this->_get_bundle_fqcn($this->bundle_name); - $definition = $fqcn::define(); - - // Add CDN assets from the bundle definition (same format as main resolution) - if (!empty($definition['cdn_assets'])) { - if (!empty($definition['cdn_assets']['js'])) { - $this->cdn_assets['js'] = array_merge($this->cdn_assets['js'], $definition['cdn_assets']['js']); - } - if (!empty($definition['cdn_assets']['css'])) { - $this->cdn_assets['css'] = array_merge($this->cdn_assets['css'], $definition['cdn_assets']['css']); - } - } - - // Also check for Asset Bundle includes that may have CDN assets - foreach ($definition['include'] ?? [] as $include) { - $this->_collect_cdn_assets_from_include($include); - } - } - - /** - * Collect CDN assets from a bundle include without full resolution - */ - protected function _collect_cdn_assets_from_include($include): void - { - // Handle config array format (from bundle aliases like jquery, lodash) - if (is_array($include) && isset($include['cdn'])) { - foreach ($include['cdn'] as $cdn_item) { - // Determine type from URL extension - $url = $cdn_item['url'] ?? ''; - $type = str_ends_with($url, '.css') ? 'css' : 'js'; - $this->cdn_assets[$type][] = $cdn_item; - } - return; - } - - // Handle class name includes (could be Asset Bundles with CDN assets) - // Normalize FQCN to simple name for manifest lookup (manifest stores simple names) - if (is_string($include)) { - $simple_name = Manifest::_normalize_class_name($include); - if (isset(Manifest::$data['data']['php_classes'][$simple_name])) { - if (Manifest::php_is_subclass_of($simple_name, 'Rsx_Asset_Bundle_Abstract')) { - $asset_def = $include::define(); - if (!empty($asset_def['cdn_assets'])) { - if (!empty($asset_def['cdn_assets']['js'])) { - $this->cdn_assets['js'] = array_merge($this->cdn_assets['js'], $asset_def['cdn_assets']['js']); - } - if (!empty($asset_def['cdn_assets']['css'])) { - $this->cdn_assets['css'] = array_merge($this->cdn_assets['css'], $asset_def['cdn_assets']['css']); - } - } - } - } - } - } - /** * Process required bundles (jquery, lodash, jqhtml) */ @@ -2459,18 +2389,27 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '', */ protected function _prepare_cdn_assets(array $assets, string $type): array { - // In development mode, return as-is (use CDN URLs directly) + // Deduplicate by URL (same asset may be included via multiple bundles) + $seen_urls = []; + $deduplicated = []; + foreach ($assets as $asset) { + $url = $asset['url'] ?? ''; + if (empty($url) || isset($seen_urls[$url])) { + continue; + } + $seen_urls[$url] = true; + $deduplicated[] = $asset; + } + + // In development mode, return deduplicated (use CDN URLs directly) if (!$this->is_production) { - return $assets; + return $deduplicated; } // In production-like modes, ensure cached and add filename $prepared = []; - foreach ($assets as $asset) { - $url = $asset['url'] ?? ''; - if (empty($url)) { - continue; - } + foreach ($deduplicated as $asset) { + $url = $asset['url']; // Ensure the asset is cached (downloads if not already) Cdn_Cache::get($url, $type);