diff --git a/.cursor/rules/enqueues.mdc b/.cursor/rules/enqueues.mdc new file mode 100644 index 0000000..099e3af --- /dev/null +++ b/.cursor/rules/enqueues.mdc @@ -0,0 +1,291 @@ +--- +alwaysApply: true +--- + +# Enqueues MU Plugin - Usage Guide + +## Overview + +The Enqueues system (`wp-content/mu-plugins/**/build-tools/vendor/thecodeco/enqueues/`) automates asset loading based on page type, template, and post type. It provides automatic fallback to default assets, extensive filter hooks for customisation, and **high-performance caching** to minimize filesystem operations. + +### Key Performance Features + +- **Intelligent Caching**: All asset lookups, block registry scans, and template discoveries are cached with automatic invalidation based on build signatures +- **Build Signature Auto-Invalidation**: Cache keys include file modification times of main assets, so caches automatically bust on new deployments +- **Request-Level Guards**: Block registration runs only once per request, preventing redundant filesystem operations +- **Object Cache Compatible**: Uses WordPress transients which automatically leverage object cache (Redis/Memcached) when available + +## Initialisation + +Enqueues controllers are initialised in the theme enqueue controller: + +```php +// Location: wp-content/mu-plugins/fujifilm/fujifilm-core/source/php/Controller/ThemeEnqueueController.php +enqueues_initialize_controllers( 'theme' ); +``` + +## Caching Configuration + +Caching is enabled by default and uses a 12-hour TTL. To configure: + +```php +// Define in wp-config.php or bootstrap file +define( 'ENQUEUES_CACHE_ENABLED', true ); +define( 'ENQUEUES_CACHE_TTL', 12 * HOUR_IN_SECONDS ); +``` + +**Cache Flush Helper:** +```php +// Flush all Enqueues caches (useful after deployments) +\Enqueues\flush_enqueues_cache(); +``` + +## Theme Asset Loading + +The system automatically loads CSS/JS files based on: +- Page type (front-page, archive, single, etc.) +- Template name +- Post type + +**File Structure:** +- Source files: `wp-content/themes/fujifilm/source/js/`, `wp-content/themes/fujifilm/source/css/` +- Built assets: `wp-content/themes/fujifilm/dist/js/`, `wp-content/themes/fujifilm/dist/css/` + +**Naming Convention:** +- Main entry: `main.js` / `main.css` (configurable via `enqueues_theme_default_enqueue_asset_filename` filter) +- Page-specific: `{page-type}.js` / `{page-type}.css` (e.g., `front-page.js`, `archive.js`) +- Template-specific: `template-{template-name}.js` / `template-{template-name}.css` +- Post type-specific: `single-{post-type}.js` / `single-{post-type}.css` + +**Performance Note:** All asset lookups are cached, including negative lookups (file not found), to avoid repeated filesystem operations. + +## Helper Functions + +### Find Asset File Path + +Cached function that finds the correct asset path (minified or standard) based on environment: + +```php +$js_path = \Enqueues\asset_find_file_path( '/dist/js', 'filename', 'js', $directory ); +$css_path = \Enqueues\asset_find_file_path( '/source/css', 'filename', 'css', $directory ); +``` + +**Returns:** Relative web-accessible path (e.g., `/dist/js/filename.min.js`) or empty string if not found. + +**Caching:** Results are cached with build signature, so lookups are only performed once per build. + +### Get Asset Page Type File Data + +Cached function that retrieves complete asset data including dependencies and version: + +```php +$js_data = \Enqueues\get_asset_page_type_file_data( + $directory, + $directory_uri, + 'js', // directory_part + 'filename', // file_name + 'main', // fallback_file_name (optional) + 'js', // file_ext + "Missing filename JS file." // missing_local_warning +); +``` + +**Returns:** Array with: +- `handle` - Asset handle (sanitized filename) +- `url` - Full asset URL +- `file` - Full file path +- `ver` - File modification time (version) +- `minified` - Boolean indicating if file is minified +- `asset_php` - Asset PHP data (dependencies, version) for JS files + +**Caching:** Results are cached with build signature, including negative results to avoid repeated lookups. + +## Filters for Customisation + +### Main Theme Asset Filters + +**JS Dependencies:** +```php +add_filter( 'enqueues_theme_js_dependencies_main', function( $dependencies ) { + $dependencies[] = 'wp-i18n'; + return $dependencies; +}, 10, 1 ); +``` + +**JS Localized Data Variable Name:** +```php +add_filter( 'enqueues_theme_js_localized_data_var_name_main', function( $var_name ) { + if ( is_page( 'compare-cameras' ) ) { + $var_name = 'compare_i18n'; + } + return $var_name; +}, 10, 1 ); +``` + +**JS Localized Data:** +```php +add_filter( 'enqueues_theme_js_localized_data_main', function( $localized_data ) { + if ( is_page( 'compare-cameras' ) ) { + $localised_specifications = new Specifications(); + $localized_data = array_merge( $localized_data, $localised_specifications->localized() ); + } + return $localized_data; +}, 10, 1 ); +``` + +**Default Asset Filename:** +```php +add_filter( 'enqueues_theme_default_enqueue_asset_filename', function( $filename ) { + // Change default from 'main' to something else + return 'custom-main'; +}, 10, 1 ); +``` + +## Manual Asset Registration + +For assets not handled automatically by Enqueues, use standard WordPress functions with Enqueues helpers: + +```php +$js_data = \Enqueues\get_asset_page_type_file_data( + $directory, + $directory_uri, + 'js', + $filename, + null, + 'js', + "Missing {$filename} JS file." +); + +if ( $js_data ) { + $js_handle = $js_data['handle']; + $js_src = $js_data['url']; + $asset_php = $js_data['asset_php'] ?? []; + $js_deps = $asset_php['dependencies'] ?? []; + $js_ver = $asset_php['version'] ?? $js_data['ver']; + + wp_enqueue_script( + $js_handle, + $js_src, + $js_deps, + $js_ver, + [ + 'strategy' => 'async', + 'in_footer' => true, + ] + ); +} +``` + +**Performance Note:** `get_asset_page_type_file_data()` is cached, so calling it multiple times with the same parameters will use the cache. + +## Block Editor Assets + +### Block Registration + +Blocks are automatically registered from `dist/blocks/` directory. The system: +- Scans blocks directory once per request (cached) +- Checks if blocks are already registered before calling Core's registration function +- Caches block metadata to avoid repeated glob operations +- Automatically invalidates cache when build signature changes + +**Block Structure:** +``` +dist/blocks/ + └── block-name/ + ├── block.json + ├── render.php (for dynamic blocks) + ├── style.css + ├── view.css + ├── editor.css + └── index.js +``` + +**Performance Optimizations:** +- Block registry scan is cached with build signature +- Blocks are only registered if not already in the registry +- Request-level guard prevents multiple registrations per request + +### Block Editor Asset Helpers + +For block editor assets, use Enqueues helpers to find files: + +```php +$js_editor_blocks_path = \Enqueues\asset_find_file_path( '/dist/js', 'editor_blocks', 'js', $directory ); +$css_editor_blocks_path = \Enqueues\asset_find_file_path( '/dist/css', 'editor_blocks', 'css', $directory ); +$css_blocks_path = \Enqueues\asset_find_file_path( '/dist/css', 'blocks', 'css', $directory ); +``` + +### Block Registration Cache Management + +**Flush Block Cache:** +```php +// Flush block registry cache (useful after adding/removing blocks) +do_action( 'enqueues_flush_block_cache' ); +``` + +**Automatic Invalidation:** +- Cache automatically invalidates on theme switch (`after_switch_theme` hook) +- Cache automatically invalidates when build signature changes (new deployment) + +## External Libraries + +Register external libraries (CDNs) separately: + +```php +wp_register_script( + 'youtube-iframe-api', + 'https://www.youtube.com/iframe_api', + [], + null, // External API doesn't need version + [ + 'strategy' => 'defer', + 'in_footer' => true, + ] +); +``` + +## Performance Best Practices + +1. **Always use Enqueues helpers**: `asset_find_file_path()` and `get_asset_page_type_file_data()` are cached and optimized +2. **Avoid direct filesystem calls**: Use Enqueues functions instead of `file_exists()`, `filemtime()`, etc. +3. **Leverage caching**: Results are automatically cached, so repeated calls are fast +4. **Respect build signatures**: Cache keys include build signatures, so caches auto-invalidate on deployments +5. **Use filters for customisation**: Don't bypass the system; use filters to modify behavior + +## Core Web Vitals Optimizations + +The system includes built-in CLS (Cumulative Layout Shift) prevention for dynamic blocks: + +- Dynamic block styles are pre-enqueued early (priority 1 on `wp_enqueue_scripts`) +- Styles print in `
` as cacheable `` tags instead of late discovery +- Block detection is optimized to batch `has_block()` calls + +**Note:** These optimizations are automatic and require no configuration. + +## Troubleshooting + +### Cache Not Working + +1. Check if caching is enabled: `defined( 'ENQUEUES_CACHE_ENABLED' ) && ENQUEUES_CACHE_ENABLED` +2. Verify TTL is set: `defined( 'ENQUEUES_CACHE_TTL' )` +3. Flush cache manually: `\Enqueues\flush_enqueues_cache()` + +### Blocks Not Registering + +1. Check block directory exists: `dist/blocks/` +2. Verify `block.json` files exist in each block directory +3. Check block namespace matches: Uses `enqueues_block_editor_namespace` filter +4. Flush block cache: `do_action( 'enqueues_flush_block_cache' )` + +### Assets Not Loading + +1. Verify build process has run: Check `dist/` directory for built assets +2. Check file naming matches page type/template +3. Verify fallback to `main.js`/`main.css` works +4. Check local dev warnings (only shown in local environment) + +## Additional Resources + +- Main README: `wp-content/mu-plugins/fujifilm/build-tools/vendor/thecodeco/enqueues/README.md` +- Documentation: `wp-content/mu-plugins/fujifilm/build-tools/vendor/thecodeco/enqueues/docs/` +- Source code: `wp-content/mu-plugins/fujifilm/build-tools/vendor/thecodeco/enqueues/src/` diff --git a/CHANGELOG.md b/CHANGELOG.md index f80feba..a716201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.4] - 2026-02-03 + +### Added +- **ENHANCEMENT**: Copy plugin patterns now accept custom source and destination block directories + - Added `srcBlockDir` and `distBlockDir` support for block JSON, render PHP, and block assets copying + - Added `assetsDir` support to allow copying any block directory, not just `assets` + +### Fixed +- **BUGFIX**: Removed hard-coded `/src/` path replacement for block asset copying + - Block asset output now uses the matched source path and configured directories + - Prevents incorrect destinations when `srcDirPattern` is customised + ## [1.3.3] - 2025-01-27 ### Fixed diff --git a/composer.json b/composer.json index 5c7b24d..33d3d2c 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "thecodeco/enqueues", "description": "Enqueues", - "version": "1.3.3", + "version": "1.3.4", "require": { "php": ">=8.0.0" }, diff --git a/docs/BLOCK-EDITOR.md b/docs/BLOCK-EDITOR.md index 57106b1..743893f 100644 --- a/docs/BLOCK-EDITOR.md +++ b/docs/BLOCK-EDITOR.md @@ -1,7 +1,7 @@ # Block Editor Features ## What Is Block Editor Integration? -The Enqueues MU Plugin makes it easy to register custom blocks, block categories, and block editor plugins for the WordPress block editor (Gutenberg). You can control which scripts and styles are loaded in the editor, localize data, and more. +The Enqueues MU Plugin makes it easy to register custom blocks, block categories, and block editor plugins for the WordPress block editor (Gutenberg). You can control which scripts and styles are loaded in the editor, localise data, and more. ## CLS (Cumulative Layout Shift) Fix for Dynamic Blocks @@ -21,8 +21,8 @@ The Enqueues system implements a comprehensive fix: 1. **Let Core own block registration**: Use `register_block_type_from_metadata()` so Core registers the correct handles from `block.json` 2. **Detect dynamic blocks**: Identify blocks with `render.php` or `"render"` in `block.json` during registration 3. **Pre-enqueue styles early**: On `wp_enqueue_scripts` (priority 1), check if dynamic blocks are present on the page and enqueue their styles -4. **Core Web Vitals optimization**: Use filters to ensure styles load as `` tags in `` -5. **Add localized parameters**: Use Core's registered handles to localize data for block scripts +4. **Core Web Vitals optimisation**: Use filters to ensure styles load as `` tags in `` +5. **Add localised parameters**: Use Core's registered handles to localise data for block scripts ### Benefits - ✅ **Eliminates CLS** from dynamic block styles @@ -30,7 +30,7 @@ The Enqueues system implements a comprehensive fix: - ✅ **Works with custom handles**: Respects custom style handles defined in `block.json` - ✅ **Performance-friendly**: Only loads CSS for blocks present on the page - ✅ **Maintains existing functionality**: Plugin/extension asset handling unchanged -- ✅ **Supports localized parameters**: Blocks can have localized data even without registering their own scripts +- ✅ **Supports localised parameters**: Blocks can have localised data even without registering their own scripts ### Implementation Details The fix is implemented in `BlockEditorRegistrationController`: @@ -42,7 +42,7 @@ The fix is implemented in `BlockEditorRegistrationController`: - `should_load_separate_core_block_assets = true`: Only load CSS for blocks on page - `wp_should_inline_block_styles = false`: Use `` tags instead of inline ` + - + get_request_id(), + ]; + + // Add URL if available (avoid calling too early in WordPress load). + if ( function_exists( 'home_url' ) && isset( $_SERVER['REQUEST_URI'] ) ) { + $context['url'] = home_url( $_SERVER['REQUEST_URI'] ); + } elseif ( isset( $_SERVER['REQUEST_URI'] ) ) { + $context['uri'] = $_SERVER['REQUEST_URI']; + } + + // Add page type if available. + if ( function_exists( 'get_page_type' ) ) { + $context['page_type'] = get_page_type(); + } + + return $context; +} + +/** + * Log debug message for Enqueues system. + * + * Includes request ID and page context to help group log entries by request. + * + * @param string $message Debug message. + * @param array $context Optional context data to include. + * @return void + */ +function debug_log( string $message, array $context = [] ): void { + if ( ! is_debug_enabled() ) { + return; + } + + // Get page context (request ID, URL, etc.). + $page_context = get_page_context(); + + // Merge page context with provided context. + $full_context = array_merge( $page_context, $context ); + + $log_message = '[ENQUEUES] [' . $page_context['request_id'] . '] ' . $message; + if ( ! empty( $full_context ) ) { + // Remove request_id from context since it's already in the message prefix. + unset( $full_context['request_id'] ); + if ( ! empty( $full_context ) ) { + $log_message .= ' | Context: ' . wp_json_encode( $full_context, JSON_PRETTY_PRINT ); + } + } + + // Use error_log with message type 0 (system logger) to ensure it goes to the configured error log. + error_log( $log_message, 0 ); +} + /** * Determines whether caching is enabled for asset loading. * @@ -18,12 +108,45 @@ * @return bool True if caching is enabled, false otherwise. */ function is_cache_enabled(): bool { + static $result = null; + static $logged = false; + + // Memoize result per request to avoid repeated filter calls. + if ( null !== $result ) { + return $result; + } + + // If the constant is set, respect it and skip further checks. + if ( defined( 'ENQUEUES_CACHE_ENABLED' ) ) { + $result = (bool) ENQUEUES_CACHE_ENABLED; + + if ( ! $logged && is_debug_enabled() ) { + error_log( '[ENQUEUES] Debug logging is ENABLED - Enqueues system is active' ); + debug_log( 'is_cache_enabled() - Initialized', [ 'enabled' => $result, 'constant' => ENQUEUES_CACHE_ENABLED ] ); + $logged = true; + } + + return $result; + } + + // If the site is local, disable caching by default. + $cache_enabled = is_local() ? false : true; + /** * Filters whether caching is enabled in the Enqueues plugin. * * @param bool $is_cache_enabled True if caching is enabled, false otherwise. */ - return (bool) apply_filters( 'enqueues_is_cache_enabled', defined( 'ENQUEUES_CACHE_ENABLED' ) && ENQUEUES_CACHE_ENABLED ); + $result = (bool) apply_filters( 'enqueues_is_cache_enabled', $cache_enabled ); + + // Log once per request to verify debug logging is working. + if ( ! $logged && is_debug_enabled() ) { + error_log( '[ENQUEUES] Debug logging is ENABLED - Enqueues system is active' ); + debug_log( 'is_cache_enabled() - Initialized', [ 'enabled' => $result, 'constant' => 'not defined' ] ); + $logged = true; + } + + return $result; } /** @@ -40,5 +163,149 @@ function get_cache_ttl(): int { * * @param int $cache_ttl The TTL in seconds. Defaults to 1 day (DAY_IN_SECONDS). */ - return (int) apply_filters( 'enqueues_cache_ttl', defined( 'ENQUEUES_CACHE_TTL' ) ? ENQUEUES_CACHE_TTL : DAY_IN_SECONDS ); + $ttl = (int) apply_filters( 'enqueues_cache_ttl', defined( 'ENQUEUES_CACHE_TTL' ) ? ENQUEUES_CACHE_TTL : DAY_IN_SECONDS ); + + debug_log( 'get_cache_ttl()', [ 'ttl_seconds' => $ttl, 'ttl_hours' => round( $ttl / 3600, 2 ) ] ); + + return $ttl; +} + +/** + * Gets the build signature based on main asset file modification times. + * + * This signature changes whenever the main CSS/JS files are rebuilt, allowing + * cache entries to automatically invalidate on new deployments. + * + * Uses the enqueues_theme_default_enqueue_asset_filename filter to determine + * the main asset filename, ensuring the signature reflects the actual theme configuration. + * + * @return string Build signature hash. + */ +function get_enqueues_build_signature(): string { + static $signature = null; + static $logged = false; + + // Memoize per request to avoid repeated transient lookups. + if ( null !== $signature ) { + return $signature; + } + + $cache_key = 'enqueues_build_signature'; + $cached = is_cache_enabled() ? get_transient( $cache_key ) : false; + + if ( false !== $cached && is_array( $cached ) ) { + $signature = $cached['signature'] ?? ''; + $css_mtime = (int) ( $cached['css_mtime'] ?? 0 ); + $js_mtime = (int) ( $cached['js_mtime'] ?? 0 ); + $filename = (string) ( $cached['main_filename'] ?? '' ); + + // If the cached data still matches current mtimes, return it. + if ( $signature && $filename ) { + $directory = get_template_directory(); + $main_css = "{$directory}/dist/css/{$filename}.min.css"; + $main_js = "{$directory}/dist/js/{$filename}.min.js"; + + $current_css_mtime = file_exists( $main_css ) ? filemtime( $main_css ) : 0; + $current_js_mtime = file_exists( $main_js ) ? filemtime( $main_js ) : 0; + + if ( $current_css_mtime === $css_mtime && $current_js_mtime === $js_mtime ) { + if ( ! $logged && is_debug_enabled() ) { + debug_log( 'get_enqueues_build_signature()', [ 'source' => 'transient_cache', 'signature' => substr( $signature, 0, 8 ) . '...' ] ); + $logged = true; + } + return $signature; + } + } + } + + $directory = get_template_directory(); + + // Use the filter to get the actual main filename (respects theme/child site configuration). + /** + * Filter the default asset filename for the theme. + * + * @param string $filename The default asset filename. + */ + $main_filename = apply_filters( 'enqueues_theme_default_enqueue_asset_filename', 'main' ); + + $main_css = "{$directory}/dist/css/{$main_filename}.min.css"; + $main_js = "{$directory}/dist/js/{$main_filename}.min.js"; + + $mtime_css = file_exists( $main_css ) ? filemtime( $main_css ) : 0; + $mtime_js = file_exists( $main_js ) ? filemtime( $main_js ) : 0; + + // Create signature from both file modification times and main filename. + $signature = md5( "{$main_filename}:{$mtime_css}:{$mtime_js}" ); + + if ( is_debug_enabled() ) { + debug_log( 'get_enqueues_build_signature()', [ + 'source' => 'generated', + 'signature' => substr( $signature, 0, 8 ) . '...', + 'main_filename' => $main_filename, + 'css_mtime' => $mtime_css, + 'js_mtime' => $mtime_js, + 'css_exists' => file_exists( $main_css ), + 'js_exists' => file_exists( $main_js ), + ] ); + } + + // Cache the signature for 1 week. + // The signature will change when files are rebuilt durring build process or TTL expires. + if ( is_cache_enabled() ) { + set_transient( + $cache_key, + [ + 'signature' => $signature, + 'main_filename' => $main_filename, + 'css_mtime' => $mtime_css, + 'js_mtime' => $mtime_js, + ], + WEEK_IN_SECONDS, + ); + } + + return $signature; +} + +/** + * Flushes all Enqueues-related transients and cache entries. + * + * This function deletes all transients and cache entries that start with the Enqueues prefix, + * allowing for manual cache invalidation when needed (e.g., after deployments). + * + * @return int Number of cache entries deleted. + */ +function flush_enqueues_cache(): int { + global $wpdb; + + $deleted = 0; + + // Delete transients that match Enqueues cache keys. + $transient_prefixes = [ + '_transient_enqueues_', + '_transient_timeout_enqueues_', + ]; + + foreach ( $transient_prefixes as $prefix ) { + $like_pattern = $wpdb->esc_like( $prefix ) . '%'; + $query = $wpdb->prepare( + "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", + $like_pattern, + ); + $deleted += $wpdb->query( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + } + + // Also clear object cache if available. + if ( function_exists( 'wp_cache_flush_group' ) ) { + wp_cache_flush_group( 'enqueues' ); + } + + /** + * Fires after Enqueues cache has been flushed. + * + * @param int $deleted Number of cache entries deleted. + */ + do_action( 'enqueues_cache_flushed', $deleted ); + + return $deleted; } diff --git a/src/php/Function/Env.php b/src/php/Function/Env.php index 6e31feb..df3f648 100644 --- a/src/php/Function/Env.php +++ b/src/php/Function/Env.php @@ -198,7 +198,7 @@ function is_development() { function is_local() { // Bail early if this site has defined a custom function. - if ( function_exists( 'Enqueues\\is_local' ) ) { + if ( function_exists( 'is_local' ) ) { return \is_local(); } diff --git a/src/php/Library/EnqueueAssets.php b/src/php/Library/EnqueueAssets.php index 76e6b1a..facac31 100644 --- a/src/php/Library/EnqueueAssets.php +++ b/src/php/Library/EnqueueAssets.php @@ -15,6 +15,8 @@ use function Enqueues\get_page_type; use function Enqueues\is_cache_enabled; use function Enqueues\string_slugify; +use function Enqueues\get_enqueues_build_signature; +use function Enqueues\debug_log; /** * Class responsible for enqueuing the theme's main stylesheet and scripts based on page type, template, or post type. @@ -170,14 +172,28 @@ public function get_theme_default_enqueue_asset_filename(): string { * It also checks registered post types and includes files for them if they exist. * * Caching Strategy: - * - Caches the allowed page types and templates for 24 hours using WordPress transients. - * - The cache is keyed based on the page types and templates to ensure uniqueness. + * - Caches the allowed page types and templates for the configured TTL using WordPress transients. + * - The cache is keyed based on the build signature and locale/site to ensure uniqueness. * - Cache improves performance by avoiding repeated file system lookups on every request. * * @return array List of allowed page types and templates. */ public function get_enqueues_theme_allowed_page_types_and_templates(): array { + // Build cache key with build signature and locale/site for auto-invalidation. + $build_signature = get_enqueues_build_signature(); + $locale = function_exists( 'get_locale' ) ? get_locale() : 'default'; + $site_id = get_current_blog_id(); + $cache_key = 'enqueues_allowed_page_types_' . md5( "{$build_signature}:{$locale}:{$site_id}" ); + + // Check cache first. + if ( is_cache_enabled() ) { + $cached_allowed = get_transient( $cache_key ); + if ( false !== $cached_allowed ) { + return $cached_allowed; + } + } + // Get theme directory path. $theme_directory = get_template_directory(); @@ -228,15 +244,22 @@ function ( $post_type ) { * * @param array $allowed The default allowed page types and templates. */ - return apply_filters( 'enqueues_theme_allowed_page_types_and_templates', $allowed ); + $allowed = apply_filters( 'enqueues_theme_allowed_page_types_and_templates', $allowed ); + + // Cache the result. + if ( is_cache_enabled() ) { + set_transient( $cache_key, $allowed, get_cache_ttl() ); + } + + return $allowed; } /** * Get template files from the theme directory. * * Caching Strategy: - * - Results are cached for 24 hours to avoid repeated file system access. - * - The cache key is static (`enqueues_theme_template_files`) because the content doesn't change frequently. + * - Results are cached for the configured TTL to avoid repeated file system access. + * - The cache key includes the build signature to auto-invalidate on deployments. * - This reduces I/O operations and improves page load performance. * * @param string $theme_directory The path to the theme directory. @@ -245,13 +268,21 @@ function ( $post_type ) { */ protected function get_theme_template_files( string $theme_directory ): array { - // Try to get the cached value first. - $cache_key = 'enqueues_theme_template_files'; - $template_files = is_cache_enabled() ? get_transient( $cache_key ) : false; - if ( $template_files ) { + // Build cache key with build signature for auto-invalidation. + $build_signature = get_enqueues_build_signature(); + $cache_key = 'enqueues_theme_template_files_' . $build_signature; + $template_files = is_cache_enabled() ? get_transient( $cache_key ) : false; + if ( false !== $template_files ) { + debug_log( 'get_theme_template_files() - CACHE HIT', [ + 'template_count' => count( $template_files ), + ] ); return $template_files; } + debug_log( 'get_theme_template_files() - CACHE MISS - Scanning templates', [ + 'theme_directory' => $theme_directory, + ] ); + $template_files = []; // Look for .php files in the theme's root and subdirectories like 'template-parts', excluding build-tools and dist directories. @@ -277,7 +308,7 @@ protected function get_theme_template_files( string $theme_directory ): array { * @param array $directories The array of directories to skip being scanned for template files. */ $directories = apply_filters( 'enqueues_theme_skip_scan_directories', $directories ); - + // Skip files in the specified directories. foreach ( $directories as $dir ) { if ( strpos( $file_path, $dir ) !== false ) { @@ -300,6 +331,10 @@ protected function get_theme_template_files( string $theme_directory ): array { set_transient( $cache_key, $template_files, get_cache_ttl() ); } + debug_log( 'get_theme_template_files() - Scan complete, cached', [ + 'template_count' => count( $template_files ), + ] ); + return $template_files; } @@ -307,8 +342,9 @@ protected function get_theme_template_files( string $theme_directory ): array { * Get asset files from the theme directory corresponding to known page types and templates. * * Caching Strategy: - * - Results are cached for 24 hours. - * - A unique cache key is generated using the MD5 hash of the known files array to ensure the cache key is unique to the combination of page types and templates. + * - Results are cached for the configured TTL. + * - A unique cache key is generated using the MD5 hash of the known files array and build signature + * to ensure the cache key is unique to the combination of page types, templates, and current build. * * @param string $theme_directory The path to the theme directory. * @param array $known_files Array of known page types and template filenames. @@ -316,13 +352,21 @@ protected function get_theme_template_files( string $theme_directory ): array { */ protected function get_enqueue_asset_files( string $theme_directory, array $known_files ): array { - // Cache key based on known files for uniqueness. - $cache_key = 'enqueues_asset_files_' . md5( wp_json_encode( $known_files ) ); + // Build cache key with build signature for auto-invalidation. + $build_signature = get_enqueues_build_signature(); + $cache_key = 'enqueues_asset_files_' . md5( wp_json_encode( $known_files ) . $build_signature ); $enqueue_asset_files = is_cache_enabled() ? get_transient( $cache_key ) : false; - if ( $enqueue_asset_files ) { + if ( false !== $enqueue_asset_files ) { + debug_log( 'get_enqueue_asset_files() - CACHE HIT', [ + 'asset_count' => count( $enqueue_asset_files ), + ] ); return $enqueue_asset_files; } + debug_log( 'get_enqueue_asset_files() - CACHE MISS - Scanning assets', [ + 'known_files_count' => count( $known_files ), + ] ); + $enqueue_asset_files = []; /** @@ -355,6 +399,10 @@ protected function get_enqueue_asset_files( string $theme_directory, array $know set_transient( $cache_key, $enqueue_asset_files, get_cache_ttl() ); } + debug_log( 'get_enqueue_asset_files() - Scan complete, cached', [ + 'asset_count' => count( $enqueue_asset_files ), + ] ); + return $enqueue_asset_files; } @@ -366,7 +414,7 @@ protected function get_enqueue_asset_files( string $theme_directory, array $know * @return bool */ public function render_css_inline( $css_handle = '' ): bool { - + /** * Filter to render CSS inline. *