Deduplicate CDN assets by URL in bundle compiler

Unify CDN asset collection between dev and prod modes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-14 23:25:10 +00:00
parent 2711c60284
commit e389093a0a
2 changed files with 26 additions and 97 deletions

View File

@@ -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)');

View File

@@ -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);