From fd494da0c0931181a5c2d0b3742a1a14efd98b2b Mon Sep 17 00:00:00 2001 From: Anthony Thorne Date: Mon, 8 Dec 2025 15:37:20 +0700 Subject: [PATCH 1/9] Optimise Enqueues with intelligent caching and performance - Add caching for asset lookups, template scans, and block registry - Implement build signature auto-invalidation for cache busting - Add request-level guards to prevent redundant block registrations - Optimise dynamic block detection with batched has_block() calls - Add cache flush helper and update documentation - Create vendor-specific cursor rule file Reduces filesystem operations from hundreds to cached lookups per request. --- .cursor/rules/enqueues.mdc | 291 ++++++++++++++++++ docs/BLOCK-EDITOR.md | 36 ++- docs/FILTERS.md | 16 +- docs/THEME-ASSETS.md | 46 ++- .../BlockEditorRegistrationController.php | 216 +++++++++---- src/php/Function/Assets.php | 67 +++- src/php/Function/Cache.php | 98 +++++- src/php/Library/EnqueueAssets.php | 53 +++- 8 files changed, 740 insertions(+), 83 deletions(-) create mode 100644 .cursor/rules/enqueues.mdc diff --git a/.cursor/rules/enqueues.mdc b/.cursor/rules/enqueues.mdc new file mode 100644 index 0000000..a7bf894 --- /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/fujifilm/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/docs/BLOCK-EDITOR.md b/docs/BLOCK-EDITOR.md index 57106b1..4bc94c3 100644 --- a/docs/BLOCK-EDITOR.md +++ b/docs/BLOCK-EDITOR.md @@ -77,6 +77,40 @@ When implementing this fix: - You can add custom block categories or plugins using filters. - Block editor assets (JS/CSS) are loaded automatically based on naming conventions. +## Performance Optimizations + +### Block Registration Caching + +The system includes several performance optimizations to minimize filesystem operations during block registration: + +**Cached Block Registry Scan:** +- Block directory scan is cached with build signature for auto-invalidation +- Block metadata (handles, dynamic flags) is cached to avoid repeated glob operations +- Cache automatically invalidates when build signature changes (new deployment) + +**Request-Level Guards:** +- Block registration runs only once per request (static flag prevents multiple executions) +- Blocks are checked against the registry before calling `register_block_type_from_metadata()` +- Skips expensive filesystem lookups if blocks are already registered + +**Optimized Dynamic Block Detection:** +- `has_block()` calls are batched by concatenating post content once per query +- Reduces the number of string searches when detecting which blocks are present on a page + +**Cache Management:** +```php +// Flush block registry cache (useful after adding/removing blocks) +do_action( 'enqueues_flush_block_cache' ); + +// Automatic invalidation on theme switch +// Cache automatically invalidates when build signature changes +``` + +**Performance Impact:** +- Reduces filesystem operations from hundreds per request to a single cached lookup +- Eliminates redundant calls to `register_block_type_from_metadata()` and its nested functions +- Significantly improves performance on high-traffic multisite installations + ## Using Filters for Block Assets ### Block Localization Filters @@ -304,4 +338,4 @@ This allows you to: **Why use this?** - Keeps block assets organized and scalable - Makes it easy to register and load blocks, plugins, and extensions automatically -- Ensures compatibility with the Enqueues block registration system \ No newline at end of file +- Ensures compatibility with the Enqueues block registration system diff --git a/docs/FILTERS.md b/docs/FILTERS.md index e958e5c..ca847ce 100644 --- a/docs/FILTERS.md +++ b/docs/FILTERS.md @@ -55,10 +55,12 @@ This page lists **all** filters and actions available in the Enqueues MU Plugin, ## Block Editor **Note**: Block editor filters are separated by asset type: -- **Blocks**: Managed by WordPress Core via `block.json`. Only localization filters are available. +- **Blocks**: Managed by WordPress Core via `block.json`. Only localization filters are available. Block registration is cached for performance. - **Plugins/Extensions**: Fully managed by Enqueues with complete filter control. - **All**: Apply to the entire block editor system. +**Performance Note**: Block registration includes request-level guards and caching to minimize filesystem operations. See [Performance Optimizations](BLOCK-EDITOR.md#performance-optimizations) for details. + | Filter/Action | Summary | Asset Type | Docs | |:------------------------------------------------------|:---------------------------------------------------------------|:-----------|:------------------------------------------------------------| | `enqueues_block_editor_js_localized_data_blocks_{block_slug}` | Filter localized data for block scripts. | **Blocks** | [Block Editor Filters](BLOCK-EDITOR.md#block-localization-filters) | @@ -93,8 +95,14 @@ This page lists **all** filters and actions available in the Enqueues MU Plugin, | Filter/Action | Summary | Docs | |:-----------------------------------|:------------------------------------------------|:---------------------------------------------| -| `enqueues_is_cache_enabled` | Filter whether caching is enabled. | [Caching](THEME-ASSETS.md#caching) | -| `enqueues_cache_ttl` | Filter the cache time-to-live. | [Caching](THEME-ASSETS.md#caching) | +| `enqueues_is_cache_enabled` | Filter whether caching is enabled. | [Caching](THEME-ASSETS.md#caching--performance) | +| `enqueues_cache_ttl` | Filter the cache time-to-live (TTL) in seconds. | [Caching](THEME-ASSETS.md#caching--performance) | +| `enqueues_cache_flushed` | Action fired after cache flush completes. | [Caching](THEME-ASSETS.md#caching--performance) | +| `enqueues_flush_block_cache` | Action to flush block registry cache. | [Block Editor](BLOCK-EDITOR.md#performance-optimizations) | + +**Cache Helper Functions:** +- `\Enqueues\flush_enqueues_cache()`: Flush all Enqueues-related caches +- `\Enqueues\get_enqueues_build_signature()`: Get current build signature (internal) --- @@ -148,4 +156,4 @@ This page lists **all** filters and actions available in the Enqueues MU Plugin, **For a full description and usage examples, see the linked docs for each filter/action.** -*If you find a filter/action in the codebase that is not listed here, please open an issue or PR to help us keep this index up to date!* \ No newline at end of file +*If you find a filter/action in the codebase that is not listed here, please open an issue or PR to help us keep this index up to date!* diff --git a/docs/THEME-ASSETS.md b/docs/THEME-ASSETS.md index ba707a4..a891d7a 100644 --- a/docs/THEME-ASSETS.md +++ b/docs/THEME-ASSETS.md @@ -103,7 +103,41 @@ add_filter( 'enqueues_render_js_inline', function( $inline, $handle ) { }, 10, 2 ); ``` -## Caching Filters +## Caching & Performance + +The Enqueues system includes comprehensive caching to minimize filesystem operations and improve performance, especially on high-traffic multisite installations. + +### Caching Strategy + +**Automatic Caching:** +- All asset lookups (`asset_find_file_path()`, `get_asset_page_type_file_data()`) are cached +- Template file scans are cached +- Asset file discovery is cached +- Block registry scans are cached +- Negative lookups (file not found) are also cached to avoid repeated checks + +**Build Signature Auto-Invalidation:** +- Cache keys include a build signature derived from main asset file modification times +- When assets are rebuilt (new deployment), the signature changes and caches automatically invalidate +- No manual cache flushing required after deployments +- Build signature respects the `enqueues_theme_default_enqueue_asset_filename` filter + +**Cache Configuration:** +```php +// Enable caching (default: true) +define( 'ENQUEUES_CACHE_ENABLED', true ); + +// Set cache TTL in seconds (default: 12 hours) +define( 'ENQUEUES_CACHE_TTL', 12 * HOUR_IN_SECONDS ); +``` + +**Manual Cache Flush:** +```php +// Flush all Enqueues caches +\Enqueues\flush_enqueues_cache(); +``` + +### Caching Filters - `enqueues_is_cache_enabled`: Enable/disable caching for asset lookups. Example: ```php add_filter( 'enqueues_is_cache_enabled', '__return_false' ); // Disable caching in dev @@ -113,6 +147,14 @@ add_filter( 'enqueues_is_cache_enabled', '__return_false' ); // Disable caching add_filter( 'enqueues_cache_ttl', function() { return 3600; }); // 1 hour ``` +### 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 + ## Directory & File Extension Filters - `enqueues_theme_allowed_page_types_and_templates`: Control which page types/templates are scanned for assets. - `enqueues_theme_skip_scan_directories`: Skip directories when scanning for templates. Example: @@ -153,4 +195,4 @@ add_filter( 'enqueues_load_controller', function( $load, $controller, $context ) } return $load; }, 10, 3 ); -``` \ No newline at end of file +``` diff --git a/src/php/Controller/BlockEditorRegistrationController.php b/src/php/Controller/BlockEditorRegistrationController.php index 504534f..5c64c5f 100644 --- a/src/php/Controller/BlockEditorRegistrationController.php +++ b/src/php/Controller/BlockEditorRegistrationController.php @@ -51,6 +51,9 @@ use function Enqueues\get_block_editor_dist_dir; use function Enqueues\get_block_editor_categories; use function Enqueues\string_camelcaseify; +use function Enqueues\is_cache_enabled; +use function Enqueues\get_cache_ttl; +use function Enqueues\get_enqueues_build_signature; /** * Controller that integrates with the Block Editor (Gutenberg) to: @@ -91,7 +94,7 @@ class BlockEditorRegistrationController extends Controller { * * @var array{dynamic: array, static: array} */ - protected $blocks = [ + protected $blocks = [ 'dynamic' => [], 'static' => [], ]; @@ -126,6 +129,12 @@ public function set_up() { add_action( 'init', [ $this, 'register_blocks' ] ); add_filter( 'block_categories_all', [ $this, 'block_categories' ], 10, 2 ); + // Invalidate block cache on theme switch. + add_action( 'after_switch_theme', [ $this, 'flush_block_cache' ] ); + + // Allow manual cache flush via action. + add_action( 'enqueues_flush_block_cache', [ $this, 'flush_block_cache' ] ); + // Pre-enqueue dynamic block styles so they print in . add_action( 'wp_enqueue_scripts', [ $this, 'preenqueue_dynamic_block_styles' ], 1 ); @@ -143,19 +152,48 @@ public function set_up() { * We use Core's metadata registration so Core registers the correct handles from block.json. * While doing that, we detect *dynamic* blocks and remember their style handles for pre-enqueue. * + * Caching Strategy: + * - Caches the block registry scan to avoid repeated glob operations on every request. + * - Cache key includes build signature to auto-invalidate on deployments. + * - Block registration still happens via Core, but metadata is cached for performance. + * - Uses request-level static flag to ensure blocks are only processed once per request. + * * @return void */ public function register_blocks() { + // Request-level guard to prevent multiple registrations in the same request. + static $registered = false; + if ( $registered ) { + return; + } $directory = get_template_directory(); $block_editor_dist_dir_path = get_block_editor_dist_dir(); $blocks_root = "{$directory}{$block_editor_dist_dir_path}/blocks"; if ( ! is_dir( $blocks_root ) ) { + $registered = true; + return; + } + + // Build cache key with build signature for auto-invalidation. + $build_signature = get_enqueues_build_signature(); + $cache_key = 'enqueues_block_registry_' . md5( "{$blocks_root}:{$build_signature}" ); + + // Try to load cached block metadata. + $cached_blocks = is_cache_enabled() ? get_transient( $cache_key ) : false; + + if ( false !== $cached_blocks && is_array( $cached_blocks ) ) { + // Use cached block metadata, but still register blocks with Core. + $this->blocks = $cached_blocks; + $this->register_blocks_from_cache( $blocks_root ); + $registered = true; return; } - $ns = get_block_editor_namespace(); + // Cache miss: scan and register blocks. + $ns = get_block_editor_namespace(); + $block_registry = WP_Block_Type_Registry::get_instance(); foreach ( array_filter( glob( "{$blocks_root}/*" ), 'is_dir' ) as $block_dir ) { $block_name = basename( $block_dir ); @@ -170,25 +208,30 @@ public function register_blocks() { $full_name = "{$ns}/{$block_name}"; - // Let Core register default handles (style/viewStyle/editorStyle/viewScript/editorScript, etc.) - $result = register_block_type_from_metadata( $metadata_file ); + // Check if block is already registered to avoid redundant filesystem lookups. + $block_type = $block_registry->get_registered( $full_name ); - if ( ! $result ) { - if ( is_local() ) { - wp_die( sprintf( 'Block %s failed to register.', $full_name ), E_USER_ERROR ); // phpcs:ignore + if ( ! $block_type ) { + // Block not registered yet - register it with Core. + $result = register_block_type_from_metadata( $metadata_file ); + + if ( ! $result ) { + if ( is_local() ) { + wp_die( sprintf( 'Block %s failed to register.', $full_name ), E_USER_ERROR ); // phpcs:ignore + } + continue; } - continue; - } - // Pull exact handles from the registry for perfect alignment with Core. - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $full_name ); - if ( ! $block_type ) { - continue; + // Pull exact handles from the registry after registration. + $block_type = $block_registry->get_registered( $full_name ); + if ( ! $block_type ) { + continue; + } } $is_dynamic = ( isset( $block_type->render_callback ) && $block_type->render_callback ) || file_exists( "{$block_dir}/render.php" ); - $handles = [ + $handles = [ 'style_handles' => isset( $block_type->style_handles ) ? (array) $block_type->style_handles : [], 'view_style_handles' => isset( $block_type->view_style_handles ) ? (array) $block_type->view_style_handles : [], 'editor_style_handles' => isset( $block_type->editor_style_handles ) ? (array) $block_type->editor_style_handles : [], @@ -200,6 +243,71 @@ public function register_blocks() { // Track dynamic blocks for CLS prevention. $this->blocks[ $is_dynamic ? 'dynamic' : 'static' ][ $full_name ] = $handles; } + + // Cache the block metadata for future requests. + if ( is_cache_enabled() ) { + set_transient( $cache_key, $this->blocks, get_cache_ttl() ); + } + + $registered = true; + } + + /** + * Register blocks from cached metadata. + * + * When using cached block metadata, we check if blocks are already registered + * before calling register_block_type_from_metadata() to avoid expensive filesystem lookups. + * This significantly reduces the number of calls to Core's registration function. + * + * @param string $blocks_root The root directory containing blocks. + * + * @return void + */ + private function register_blocks_from_cache( string $blocks_root ): void { + $ns = get_block_editor_namespace(); + $block_registry = WP_Block_Type_Registry::get_instance(); + + foreach ( array_filter( glob( "{$blocks_root}/*" ), 'is_dir' ) as $block_dir ) { + $block_name = basename( $block_dir ); + $metadata_file = "{$blocks_root}/{$block_name}/block.json"; + + if ( ! file_exists( $metadata_file ) ) { + continue; + } + + $full_name = "{$ns}/{$block_name}"; + + // Skip registration if block is already registered (avoids expensive filesystem lookups). + if ( $block_registry->is_registered( $full_name ) ) { + continue; + } + + // Only register if not already in the registry. + register_block_type_from_metadata( $metadata_file ); + } + } + + /** + * Flush the block registry cache. + * + * Called on theme switch or can be triggered manually via the + * `enqueues_flush_block_cache` action. + * + * @return void + */ + public function flush_block_cache(): void { + $directory = get_template_directory(); + $block_editor_dist_dir_path = get_block_editor_dist_dir(); + $blocks_root = "{$directory}{$block_editor_dist_dir_path}/blocks"; + + if ( ! is_dir( $blocks_root ) ) { + return; + } + + $build_signature = get_enqueues_build_signature(); + $cache_key = 'enqueues_block_registry_' . md5( "{$blocks_root}:{$build_signature}" ); + + delete_transient( $cache_key ); } /** @@ -248,6 +356,8 @@ public function block_categories( $categories, $post ) { * Without this, dynamic block styles would be discovered during render and output * via print_late_styles() near the footer, causing visible content shifts. * + * Optimized to batch has_block() calls by concatenating content once per query. + * * @return void */ public function preenqueue_dynamic_block_styles(): void { @@ -255,42 +365,42 @@ public function preenqueue_dynamic_block_styles(): void { return; } - $maybe_enqueue = function ( $content ) { - if ( ! is_string( $content ) || '' === $content ) { - return; + $content_blob = ''; + + // Gather all content in one pass to minimize has_block() calls. + if ( is_singular() ) { + $post = get_post(); + if ( $post && isset( $post->post_content ) && is_string( $post->post_content ) ) { + $content_blob = $post->post_content; } - foreach ( $this->blocks['dynamic'] as $block_name => $handles ) { - if ( has_block( $block_name, $content ) ) { - // Enqueue all frontend style handles (style + viewStyle) so CSS prints in . - $style_handles = isset( $handles['style_handles'] ) ? (array) $handles['style_handles'] : []; - $view_style_handles = isset( $handles['view_style_handles'] ) ? (array) $handles['view_style_handles'] : []; - foreach ( array_merge( $style_handles, $view_style_handles ) as $style_handle ) { - if ( is_string( $style_handle ) && '' !== $style_handle ) { - wp_enqueue_style( $style_handle ); - } + } else { + // Archives / home: inspect main query posts (lightweight heuristic). + global $wp_query; + if ( isset( $wp_query->posts ) && is_array( $wp_query->posts ) ) { + foreach ( $wp_query->posts as $p ) { + if ( isset( $p->post_content ) && is_string( $p->post_content ) ) { + $content_blob .= $p->post_content . "\n"; } } } - }; + } - if ( is_singular() ) { - $post = get_post(); - if ( $post && isset( $post->post_content ) ) { - $maybe_enqueue( $post->post_content ); - } + if ( '' === $content_blob ) { return; } - // Archives / home: inspect main query posts (lightweight heuristic). - global $wp_query; - if ( isset( $wp_query->posts ) && is_array( $wp_query->posts ) ) { - $blob = ''; - foreach ( $wp_query->posts as $p ) { - if ( isset( $p->post_content ) && is_string( $p->post_content ) ) { - $blob .= $p->post_content . "\n"; + // Batch check all dynamic blocks against the concatenated content. + foreach ( $this->blocks['dynamic'] as $block_name => $handles ) { + if ( has_block( $block_name, $content_blob ) ) { + // Enqueue all frontend style handles (style + viewStyle) so CSS prints in . + $style_handles = isset( $handles['style_handles'] ) ? (array) $handles['style_handles'] : []; + $view_style_handles = isset( $handles['view_style_handles'] ) ? (array) $handles['view_style_handles'] : []; + foreach ( array_merge( $style_handles, $view_style_handles ) as $style_handle ) { + if ( is_string( $style_handle ) && '' !== $style_handle ) { + wp_enqueue_style( $style_handle ); + } } } - $maybe_enqueue( $blob ); } } @@ -304,13 +414,13 @@ public function preenqueue_dynamic_block_styles(): void { */ public function localize_block_scripts(): void { $context = is_admin() ? 'editor' : 'frontend'; - + // Process both static and dynamic blocks for localization. foreach ( [ 'static', 'dynamic' ] as $block_type ) { foreach ( $this->blocks[ $block_type ] as $block_name => $handles ) { $block_parts = explode( '/', $block_name ); $block_slug = end( $block_parts ); - + // Determine which script handles to localize based on context. $script_handles = []; if ( 'editor' === $context ) { @@ -331,19 +441,19 @@ public function localize_block_scripts(): void { } // Allow filtering of localized data for each block script. - $localized_data = apply_filters( - "enqueues_block_editor_js_localized_data_blocks_{$block_slug}", - [], - $context, - $script_handle + $localized_data = apply_filters( + "enqueues_block_editor_js_localized_data_blocks_{$block_slug}", + [], + $context, + $script_handle, ); if ( ! empty( $localized_data ) ) { - $localized_var_name = apply_filters( - "enqueues_block_editor_js_localized_data_var_name_blocks_{$block_slug}", - string_camelcaseify( "blockEditor blocks {$block_slug} Config" ), - $context, - $script_handle + $localized_var_name = apply_filters( + "enqueues_block_editor_js_localized_data_var_name_blocks_{$block_slug}", + string_camelcaseify( "blockEditor blocks {$block_slug} Config" ), + $context, + $script_handle, ); wp_localize_script( $script_handle, $localized_var_name, $localized_data ); @@ -446,7 +556,7 @@ private function enqueue_plugin_and_extension_assets( $type, $context, $enqueue_ ? "{$block_editor_namespace}-{$foldername}-{$js_filetype}-script" : "{$foldername}-{$js_filetype}"; - $args = [ + $args = [ 'strategy' => 'async', 'in_footer' => true, ]; diff --git a/src/php/Function/Assets.php b/src/php/Function/Assets.php index 678d968..580ff3b 100644 --- a/src/php/Function/Assets.php +++ b/src/php/Function/Assets.php @@ -9,6 +9,10 @@ namespace Enqueues; +use function Enqueues\is_cache_enabled; +use function Enqueues\get_cache_ttl; +use function Enqueues\get_enqueues_build_signature; + /** * Finds the file path for an asset based on the environment. * @@ -18,8 +22,8 @@ * * Caching Strategy: * - Caches asset file paths to avoid repeated file existence checks on every request. - * - Cache is keyed based on the relative path and file name. - * - Cached data is stored for 24 hours and automatically invalidated. + * - Cache is keyed based on the relative path, file name, directory, and build signature. + * - Cached data is stored for the configured TTL and automatically invalidated when assets are rebuilt. * * @param string $relative_path The path to the file relative to the specified directory. * @param string $file_name Name of the file without the extension. @@ -34,6 +38,18 @@ function asset_find_file_path( string $relative_path, string $file_name, string $directory = get_template_directory(); } + // Build cache key including build signature for auto-invalidation. + $build_signature = get_enqueues_build_signature(); + $cache_key = 'enqueues_asset_path_' . md5( "{$relative_path}:{$file_name}:{$file_ext}:{$directory}:{$build_signature}" ); + + // Check cache first. + if ( is_cache_enabled() ) { + $cached_path = get_transient( $cache_key ); + if ( false !== $cached_path ) { + return $cached_path; + } + } + $theme_relative_path_and_file_name = trim( $relative_path, '/' ) . '/' . trim( $file_name, '/' ); $minified = "{$directory}/{$theme_relative_path_and_file_name}.min.{$file_ext}"; @@ -49,6 +65,11 @@ function asset_find_file_path( string $relative_path, string $file_name, string $file_path = "/{$theme_relative_path_and_file_name}.{$file_ext}"; } + // Cache the result (including empty strings for negative lookups). + if ( is_cache_enabled() ) { + set_transient( $cache_key, $file_path, get_cache_ttl() ); + } + return $file_path; } @@ -86,8 +107,8 @@ function display_maybe_missing_local_warning( string $path, string $message ): v * * Caching Strategy: * - Caches asset file data to avoid repeated file existence checks and improve performance. - * - Cache is keyed based on the file name and file extension. - * - Cached data is stored for 24 hours and automatically invalidated. + * - Cache is keyed based on the file name, extension, directory, and build signature. + * - Cached data is stored for the configured TTL and automatically invalidated when assets are rebuilt. * * @param string $directory Directory path where the asset is located. * @param string $directory_uri URI of the directory for web access. @@ -109,6 +130,18 @@ function get_asset_page_type_file_data( string $missing_local_warning = 'Run the npm build for the asset files.', ): bool|array { + // Build cache key including build signature for auto-invalidation. + $build_signature = get_enqueues_build_signature(); + $cache_key = 'enqueues_asset_data_' . md5( "{$directory}:{$directory_part}:{$file_name}:{$fallback_file_name}:{$file_ext}:{$build_signature}" ); + + // Check cache first. + if ( is_cache_enabled() ) { + $cached_data = get_transient( $cache_key ); + if ( false !== $cached_data ) { + return $cached_data; + } + } + /** * Filters the source directory used for locating SCSS/SASS/CSS and JS files. * @@ -147,7 +180,7 @@ function get_asset_page_type_file_data( } if ( ! empty( $compiled_file_path ) ) { - $data = [ + $data = [ 'handle' => sanitize_key( $file_name ), 'url' => esc_url( "{$directory_uri}{$compiled_file_path}" ), 'file' => esc_url( "{$directory}{$compiled_file_path}" ), @@ -164,6 +197,11 @@ function get_asset_page_type_file_data( $data['asset_php'] = file_exists( $asset_php_path ) ? include $asset_php_path : []; } + // Cache the result. + if ( is_cache_enabled() ) { + set_transient( $cache_key, $data, get_cache_ttl() ); + } + return $data; } @@ -173,7 +211,7 @@ function get_asset_page_type_file_data( } if ( ! empty( $compiled_file_path ) ) { - $data = [ + $data = [ 'handle' => sanitize_key( $fallback_file_name ), 'url' => esc_url( "{$directory_uri}{$compiled_file_path}" ), 'file' => esc_url( "{$directory}{$compiled_file_path}" ), @@ -190,9 +228,19 @@ function get_asset_page_type_file_data( $data['asset_php'] = file_exists( $asset_php_path ) ? include $asset_php_path : []; } + // Cache the result. + if ( is_cache_enabled() ) { + set_transient( $cache_key, $data, get_cache_ttl() ); + } + return $data; } + // Cache negative result to avoid repeated lookups. + if ( is_cache_enabled() ) { + set_transient( $cache_key, false, get_cache_ttl() ); + } + return false; } @@ -308,13 +356,16 @@ function render_asset_inline( $asset ) { if ( $content && 'style' === $type ) { ?> - + - + 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/Library/EnqueueAssets.php b/src/php/Library/EnqueueAssets.php index 76e6b1a..8a4894e 100644 --- a/src/php/Library/EnqueueAssets.php +++ b/src/php/Library/EnqueueAssets.php @@ -15,6 +15,7 @@ use function Enqueues\get_page_type; use function Enqueues\is_cache_enabled; use function Enqueues\string_slugify; +use function Enqueues\get_enqueues_build_signature; /** * Class responsible for enqueuing the theme's main stylesheet and scripts based on page type, template, or post type. @@ -170,14 +171,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 +243,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,10 +267,11 @@ 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 ) { return $template_files; } @@ -307,8 +330,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,10 +340,11 @@ 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 ) { return $enqueue_asset_files; } From eb1510743af595d1123f69cd5ebd94498da0c30e Mon Sep 17 00:00:00 2001 From: Anthony Thorne Date: Mon, 8 Dec 2025 16:53:36 +0700 Subject: [PATCH 2/9] Optimise Enqueues with debug logging - Add debug logging with request ID grouping (ENQUEUES_DEBUG) --- .../BlockEditorRegistrationController.php | 50 +++++++ src/php/Function/Assets.php | 17 +++ src/php/Function/Cache.php | 134 +++++++++++++++++- src/php/Library/EnqueueAssets.php | 27 +++- 4 files changed, 224 insertions(+), 4 deletions(-) diff --git a/src/php/Controller/BlockEditorRegistrationController.php b/src/php/Controller/BlockEditorRegistrationController.php index 5c64c5f..e70fcf9 100644 --- a/src/php/Controller/BlockEditorRegistrationController.php +++ b/src/php/Controller/BlockEditorRegistrationController.php @@ -54,6 +54,7 @@ use function Enqueues\is_cache_enabled; use function Enqueues\get_cache_ttl; use function Enqueues\get_enqueues_build_signature; +use function Enqueues\debug_log; /** * Controller that integrates with the Block Editor (Gutenberg) to: @@ -185,12 +186,26 @@ public function register_blocks() { if ( false !== $cached_blocks && is_array( $cached_blocks ) ) { // Use cached block metadata, but still register blocks with Core. + $dynamic_count = isset( $cached_blocks['dynamic'] ) ? count( $cached_blocks['dynamic'] ) : 0; + $static_count = isset( $cached_blocks['static'] ) ? count( $cached_blocks['static'] ) : 0; + + debug_log( 'register_blocks() - CACHE HIT', [ + 'blocks_root' => $blocks_root, + 'dynamic_blocks' => $dynamic_count, + 'static_blocks' => $static_count, + 'total_blocks' => $dynamic_count + $static_count, + ] ); + $this->blocks = $cached_blocks; $this->register_blocks_from_cache( $blocks_root ); $registered = true; return; } + debug_log( 'register_blocks() - CACHE MISS - Scanning blocks', [ + 'blocks_root' => $blocks_root, + ] ); + // Cache miss: scan and register blocks. $ns = get_block_editor_namespace(); $block_registry = WP_Block_Type_Registry::get_instance(); @@ -213,6 +228,11 @@ public function register_blocks() { if ( ! $block_type ) { // Block not registered yet - register it with Core. + debug_log( 'register_blocks() - Registering new block', [ + 'block_name' => $full_name, + 'metadata_file' => $metadata_file, + ] ); + $result = register_block_type_from_metadata( $metadata_file ); if ( ! $result ) { @@ -227,6 +247,10 @@ public function register_blocks() { if ( ! $block_type ) { continue; } + } else { + debug_log( 'register_blocks() - Block already registered, skipping', [ + 'block_name' => $full_name, + ] ); } $is_dynamic = ( isset( $block_type->render_callback ) && $block_type->render_callback ) || file_exists( "{$block_dir}/render.php" ); @@ -249,6 +273,15 @@ public function register_blocks() { set_transient( $cache_key, $this->blocks, get_cache_ttl() ); } + $dynamic_count = isset( $this->blocks['dynamic'] ) ? count( $this->blocks['dynamic'] ) : 0; + $static_count = isset( $this->blocks['static'] ) ? count( $this->blocks['static'] ) : 0; + + debug_log( 'register_blocks() - Scan complete, cached', [ + 'dynamic_blocks' => $dynamic_count, + 'static_blocks' => $static_count, + 'total_blocks' => $dynamic_count + $static_count, + ] ); + $registered = true; } @@ -267,6 +300,9 @@ private function register_blocks_from_cache( string $blocks_root ): void { $ns = get_block_editor_namespace(); $block_registry = WP_Block_Type_Registry::get_instance(); + $registered_count = 0; + $skipped_count = 0; + foreach ( array_filter( glob( "{$blocks_root}/*" ), 'is_dir' ) as $block_dir ) { $block_name = basename( $block_dir ); $metadata_file = "{$blocks_root}/{$block_name}/block.json"; @@ -279,12 +315,26 @@ private function register_blocks_from_cache( string $blocks_root ): void { // Skip registration if block is already registered (avoids expensive filesystem lookups). if ( $block_registry->is_registered( $full_name ) ) { + $skipped_count++; + debug_log( 'register_blocks_from_cache() - Block already registered, skipping', [ + 'block_name' => $full_name, + ] ); continue; } // Only register if not already in the registry. + debug_log( 'register_blocks_from_cache() - Registering block from cache', [ + 'block_name' => $full_name, + 'metadata_file' => $metadata_file, + ] ); register_block_type_from_metadata( $metadata_file ); + $registered_count++; } + + debug_log( 'register_blocks_from_cache() - Complete', [ + 'registered_count' => $registered_count, + 'skipped_count' => $skipped_count, + ] ); } /** diff --git a/src/php/Function/Assets.php b/src/php/Function/Assets.php index 580ff3b..82e07f4 100644 --- a/src/php/Function/Assets.php +++ b/src/php/Function/Assets.php @@ -12,6 +12,7 @@ use function Enqueues\is_cache_enabled; use function Enqueues\get_cache_ttl; use function Enqueues\get_enqueues_build_signature; +use function Enqueues\debug_log; /** * Finds the file path for an asset based on the environment. @@ -46,6 +47,12 @@ function asset_find_file_path( string $relative_path, string $file_name, string if ( is_cache_enabled() ) { $cached_path = get_transient( $cache_key ); if ( false !== $cached_path ) { + debug_log( 'asset_find_file_path() - CACHE HIT', [ + 'file_name' => $file_name, + 'file_ext' => $file_ext, + 'relative_path' => $relative_path, + 'cached_path' => $cached_path, + ] ); return $cached_path; } } @@ -70,6 +77,16 @@ function asset_find_file_path( string $relative_path, string $file_name, string set_transient( $cache_key, $file_path, get_cache_ttl() ); } + debug_log( 'asset_find_file_path() - CACHE MISS', [ + 'file_name' => $file_name, + 'file_ext' => $file_ext, + 'relative_path' => $relative_path, + 'found_path' => $file_path, + 'minified_exists' => file_exists( $minified ), + 'standard_exists' => file_exists( $standard ), + 'is_local' => is_local(), + ] ); + return $file_path; } diff --git a/src/php/Function/Cache.php b/src/php/Function/Cache.php index 6b4b7f5..4483c38 100644 --- a/src/php/Function/Cache.php +++ b/src/php/Function/Cache.php @@ -13,6 +13,92 @@ exit; } +/** + * Check if Enqueues debugging is enabled. + * + * @return bool True if debugging is enabled. + */ +function is_debug_enabled(): bool { + if ( ! defined( 'ENQUEUES_DEBUG' ) ) { + return false; + } + return (bool) ENQUEUES_DEBUG; +} + +/** + * Get or generate a unique request ID for grouping log entries. + * + * @return string Request ID. + */ +function get_request_id(): string { + static $request_id = null; + + if ( null === $request_id ) { + // Generate a short unique ID for this request (8 characters). + $request_id = substr( md5( uniqid( (string) microtime( true ), true ) ), 0, 8 ); + } + + return $request_id; +} + +/** + * Get current page context for debugging. + * + * @return array Page context information. + */ +function get_page_context(): array { + $context = [ + 'request_id' => 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. * @@ -22,6 +108,13 @@ * @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; + } $cache_enabled = defined( 'ENQUEUES_CACHE_ENABLED' ) ? ENQUEUES_CACHE_ENABLED : true; @@ -30,7 +123,16 @@ function is_cache_enabled(): bool { * * @param bool $is_cache_enabled True if caching is enabled, false otherwise. */ - return (bool) apply_filters( 'enqueues_is_cache_enabled', $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' => defined( 'ENQUEUES_CACHE_ENABLED' ) ? ENQUEUES_CACHE_ENABLED : 'not defined' ] ); + $logged = true; + } + + return $result; } /** @@ -47,7 +149,11 @@ 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; } /** @@ -62,10 +168,22 @@ function get_cache_ttl(): int { * @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'; $signature = is_cache_enabled() ? get_transient( $cache_key ) : false; if ( false !== $signature ) { + if ( ! $logged && is_debug_enabled() ) { + debug_log( 'get_enqueues_build_signature()', [ 'source' => 'transient_cache', 'signature' => substr( $signature, 0, 8 ) . '...' ] ); + $logged = true; + } return $signature; } @@ -88,6 +206,18 @@ function get_enqueues_build_signature(): string { // 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 hour (it will change when files are rebuilt). if ( is_cache_enabled() ) { set_transient( $cache_key, $signature, HOUR_IN_SECONDS ); diff --git a/src/php/Library/EnqueueAssets.php b/src/php/Library/EnqueueAssets.php index 8a4894e..facac31 100644 --- a/src/php/Library/EnqueueAssets.php +++ b/src/php/Library/EnqueueAssets.php @@ -16,6 +16,7 @@ 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. @@ -272,9 +273,16 @@ protected function get_theme_template_files( string $theme_directory ): array { $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. @@ -300,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 ) { @@ -323,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; } @@ -345,9 +357,16 @@ protected function get_enqueue_asset_files( string $theme_directory, array $know $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 ( 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 = []; /** @@ -380,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; } @@ -391,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. * From a551e54e858abca3817992d6ed78402f8fa3cb8e Mon Sep 17 00:00:00 2001 From: Anthony Thorne Date: Thu, 22 Jan 2026 16:36:40 +0700 Subject: [PATCH 3/9] Add the ability to copy files asset directories to dist dir --- .cursor/rules/enqueues.mdc | 2 +- src/js/enqueues-copy-plugin-config-pattern.js | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.cursor/rules/enqueues.mdc b/.cursor/rules/enqueues.mdc index a7bf894..099e3af 100644 --- a/.cursor/rules/enqueues.mdc +++ b/.cursor/rules/enqueues.mdc @@ -6,7 +6,7 @@ alwaysApply: true ## Overview -The Enqueues system (`wp-content/mu-plugins/fujifilm/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. +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 diff --git a/src/js/enqueues-copy-plugin-config-pattern.js b/src/js/enqueues-copy-plugin-config-pattern.js index b9c0d1f..904d647 100644 --- a/src/js/enqueues-copy-plugin-config-pattern.js +++ b/src/js/enqueues-copy-plugin-config-pattern.js @@ -101,6 +101,26 @@ function getCopyPluginConfigRenderPhpPattern(rootDir, distDir, srcDirPattern = ' }; } +/** + * Generates a CopyPlugin configuration for copying Gutenberg block asset files (e.g., icons). + * + * @param {string} rootDir - The root directory path. + * @param {string} distDir - The distribution directory path. + * @param {string} srcDirPattern - The file matching pattern to copy. + * @param {string} blockDir - The directory containing the blocks within the block editor. + * @returns {Object} A CopyPlugin pattern configuration for Gutenberg block assets. + */ +function getCopyPluginConfigBlockAssetsPattern(rootDir, distDir, srcDirPattern = '**/src', blockDir = 'block-editor/blocks') { + return { + context: rootDir, + from: `${srcDirPattern}/${blockDir}/**/assets/**/*`, + to: (pathContext) => { + return pathContext.absoluteFilename.replace(`${rootDir}/src/`, `${distDir}/`); + }, + noErrorOnMissing: true, + }; +} + /** * Generates a CopyPlugin configuration pattern based on the provided context. * @@ -121,6 +141,8 @@ function enqueuesGetCopyPluginConfigPattern(rootDir, distDir, context, srcDirPat return getCopyPluginConfigBlockJsonPattern(rootDir, distDir, srcDirPattern); case 'render-php': return getCopyPluginConfigRenderPhpPattern(rootDir, distDir, srcDirPattern); + case 'block-assets': + return getCopyPluginConfigBlockAssetsPattern(rootDir, distDir, srcDirPattern); default: throw new Error(`Unknown context: ${context}`); } From d65ac043d5452aa34377d0a7342213dc7600a1fa Mon Sep 17 00:00:00 2001 From: Anthony Thorne Date: Wed, 4 Feb 2026 07:54:50 +0700 Subject: [PATCH 4/9] Refine copy plugin block directory handling add configurable src/dist block dirs and assets dir fix block asset destination paths for custom src patterns update webpack docs and AU English copy bump version to 1.3.4 and update changelog --- CHANGELOG.md | 12 +++ composer.json | 2 +- docs/THEME-ASSETS.md | 14 +-- docs/WEBPACK.md | 37 +++++++ src/js/enqueues-copy-plugin-config-pattern.js | 101 +++++++++++++++--- 5 files changed, 141 insertions(+), 25 deletions(-) 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/THEME-ASSETS.md b/docs/THEME-ASSETS.md index a891d7a..3cdf627 100644 --- a/docs/THEME-ASSETS.md +++ b/docs/THEME-ASSETS.md @@ -8,7 +8,7 @@ The Enqueues MU Plugin automatically loads CSS and JS files for each page type, - If no specific file is found, it falls back to `main.js` and `main.css`. - This ensures every page always has the necessary assets, even if you haven’t created a specific file for that context. -## Customizing Dependencies, Localization, and More +## Customising Dependencies, Localisation, and More You can use filters to: - Add or change script/style dependencies - Localize data for your scripts @@ -42,10 +42,10 @@ add_filter( 'enqueues_theme_js_localized_data_main', function( $data ) { # FILTERS FOR THEME ASSET LOADING -Below are the most important filters you can use to customize theme asset loading. Each filter is named according to the asset handle (e.g., 'main', 'single-product', etc.). +Below are the most important filters you can use to customise theme asset loading. Each filter is named according to the asset handle (e.g., 'main', 'single-product', etc.). ## CSS Filters -- `enqueues_theme_css_handle_{handle}`: Customize the handle for the style. +- `enqueues_theme_css_handle_{handle}`: Customise the handle for the style. - `enqueues_theme_css_register_style_{handle}`: Should the style be registered? Default: true. - `enqueues_theme_css_dependencies_{handle}`: Alter the style dependencies. - `enqueues_theme_css_version_{handle}`: Alter the style version. @@ -60,14 +60,14 @@ add_filter( 'enqueues_theme_css_media_main', function( $media ) { ``` ## JS Filters -- `enqueues_theme_js_handle_{handle}`: Customize the handle for the script. +- `enqueues_theme_js_handle_{handle}`: Customise the handle for the script. - `enqueues_theme_js_register_script_{handle}`: Should the script be registered? Default: true. - `enqueues_theme_js_dependencies_{handle}`: Alter the script dependencies. Default is from `.asset.php` if present. - `enqueues_theme_js_version_{handle}`: Alter the script version. Default is from `.asset.php` if present. - `enqueues_theme_js_args_{handle}`: Alter the script arguments (e.g., 'strategy', 'in_footer'). - `enqueues_theme_js_enqueue_script_{handle}`: Should the script be enqueued? Default: true. -- `enqueues_theme_js_localized_data_var_name_{handle}`: Customize the variable name for localized JS data. -- `enqueues_theme_js_localized_data_{handle}`: Customize the data array for localized JS variables. +- `enqueues_theme_js_localized_data_var_name_{handle}`: Customise the variable name for localised JS data. +- `enqueues_theme_js_localized_data_{handle}`: Customise the data array for localised JS variables. ### Example: Add a Dependency ```php @@ -87,7 +87,7 @@ add_filter( 'enqueues_theme_js_localized_data_main', function( $data ) { # MORE FILTERS & ADVANCED OPTIONS -Below are additional filters for advanced customization and performance tuning. +Below are additional filters for advanced customisation and performance tuning. ## Inline Rendering Filters - `enqueues_render_css_inline`: Should the CSS be rendered inline? Useful for critical CSS. Example: diff --git a/docs/WEBPACK.md b/docs/WEBPACK.md index 4a793fc..9eb6fc7 100644 --- a/docs/WEBPACK.md +++ b/docs/WEBPACK.md @@ -198,12 +198,49 @@ const copyPlugin = new CopyWebpackPlugin({ enqueuesGetCopyPluginConfigPattern(rootDir, distDir, 'fonts'), enqueuesGetCopyPluginConfigPattern(rootDir, distDir, 'block-json'), enqueuesGetCopyPluginConfigPattern(rootDir, distDir, 'render-php'), + enqueuesGetCopyPluginConfigPattern(rootDir, distDir, 'block-assets'), ], }); ``` This ensures block registration and server-side rendering work as expected. +To customise block source and destination directories, pass `srcBlockDir` and `distBlockDir`. You can also copy a different block directory by setting `assetsDir`. + +```js +const copyPlugin = new CopyWebpackPlugin({ + patterns: [ + enqueuesGetCopyPluginConfigPattern( + rootDir, + distDir, + 'block-assets', + '**/source', + 'blockeditor/main-blocks', + 'icons', + 'block-editor/blocks' + ), + enqueuesGetCopyPluginConfigPattern( + rootDir, + distDir, + 'block-json', + '**/source', + 'blockeditor/main-blocks', + undefined, + 'block-editor/blocks' + ), + enqueuesGetCopyPluginConfigPattern( + rootDir, + distDir, + 'render-php', + '**/source', + 'blockeditor/main-blocks', + undefined, + 'block-editor/blocks' + ), + ], +}); +``` + ## CLEANING UP BUILD OUTPUT Use `cleanAfterEveryBuildPatterns` to remove empty JS files generated for CSS-only entries: diff --git a/src/js/enqueues-copy-plugin-config-pattern.js b/src/js/enqueues-copy-plugin-config-pattern.js index 904d647..4275287 100644 --- a/src/js/enqueues-copy-plugin-config-pattern.js +++ b/src/js/enqueues-copy-plugin-config-pattern.js @@ -61,18 +61,25 @@ function getCopyPluginConfigFontPattern(rootDir, distDir, srcDirPattern = '**/sr * @param {string} rootDir - The root directory path. * @param {string} distDir - The distribution directory path. * @param {string} srcDirPattern - The file matching pattern to copy. - * @param {string} blockDir - The directory containing the blocks within the block editor. + * @param {string} srcBlockDir - The directory containing the blocks within the block editor. + * @param {string} distBlockDir - The destination directory to copy into. * @returns {Object} - A CopyPlugin pattern configuration for Gutenberg block JSON files. */ -function getCopyPluginConfigBlockJsonPattern(rootDir, distDir, srcDirPattern = '**/src', blockDir = 'block-editor/blocks' ) { +function getCopyPluginConfigBlockJsonPattern( + rootDir, + distDir, + srcDirPattern = '**/src', + srcBlockDir = 'block-editor/blocks', + distBlockDir = srcBlockDir +) { return { context: rootDir, - from: `${srcDirPattern}/${blockDir}/**/block.json`, + from: `${srcDirPattern}/${srcBlockDir}/**/block.json`, to: (pathContext) => { const segments = pathContext.absoluteFilename.split('/'); const blockName = segments[segments.length - 2]; // Replace the base src path with distDir for final destination - return `${distDir}/${blockDir}/${blockName}/block.json`; + return `${distDir}/${distBlockDir}/${blockName}/block.json`; }, noErrorOnMissing: true, }; @@ -84,18 +91,25 @@ function getCopyPluginConfigBlockJsonPattern(rootDir, distDir, srcDirPattern = ' * @param {string} rootDir - The root directory path. * @param {string} distDir - The distribution directory path. * @param {string} srcDirPattern - The file matching pattern to copy. - * @param {string} blockDir - The directory containing the blocks within the block editor. + * @param {string} srcBlockDir - The directory containing the blocks within the block editor. + * @param {string} distBlockDir - The destination directory to copy into. * @returns {Object} A CopyPlugin pattern configuration for Gutenberg block PHP render files. */ -function getCopyPluginConfigRenderPhpPattern(rootDir, distDir, srcDirPattern = '**/src', blockDir = 'block-editor/blocks' ) { +function getCopyPluginConfigRenderPhpPattern( + rootDir, + distDir, + srcDirPattern = '**/src', + srcBlockDir = 'block-editor/blocks', + distBlockDir = srcBlockDir +) { return { context: rootDir, - from: `${srcDirPattern}/${blockDir}/**/render.php`, + from: `${srcDirPattern}/${srcBlockDir}/**/render.php`, to: (pathContext) => { const segments = pathContext.absoluteFilename.split('/'); const blockName = segments[segments.length - 2]; // Replace the base src path with distDir for final destination - return `${distDir}/${blockDir}/${blockName}/render.php`; + return `${distDir}/${distBlockDir}/${blockName}/render.php`; }, noErrorOnMissing: true, }; @@ -108,14 +122,37 @@ function getCopyPluginConfigRenderPhpPattern(rootDir, distDir, srcDirPattern = ' * @param {string} distDir - The distribution directory path. * @param {string} srcDirPattern - The file matching pattern to copy. * @param {string} blockDir - The directory containing the blocks within the block editor. + * @param {string} assetsDir - The directory to copy within each block. + * @param {string} distBlockDir - The destination directory to copy into. * @returns {Object} A CopyPlugin pattern configuration for Gutenberg block assets. */ -function getCopyPluginConfigBlockAssetsPattern(rootDir, distDir, srcDirPattern = '**/src', blockDir = 'block-editor/blocks') { +function getCopyPluginConfigBlockAssetsPattern( + rootDir, + distDir, + srcDirPattern = '**/src', + srcBlockDir = 'block-editor/blocks', + assetsDir = 'assets', + distBlockDir = srcBlockDir +) { return { context: rootDir, - from: `${srcDirPattern}/${blockDir}/**/assets/**/*`, + from: `${srcDirPattern}/${srcBlockDir}/**/${assetsDir}/**/*`, to: (pathContext) => { - return pathContext.absoluteFilename.replace(`${rootDir}/src/`, `${distDir}/`); + const blockDirFragment = `/${srcBlockDir}/`; + const blockDirIndex = pathContext.absoluteFilename.lastIndexOf(blockDirFragment); + + if (-1 === blockDirIndex) { + return `${distDir}/${pathContext.absoluteFilename.split('/').pop()}`; + } + + const relativePath = pathContext.absoluteFilename.slice(blockDirIndex + 1); + + const destinationDir = distBlockDir || srcBlockDir; + const blockDirParts = srcBlockDir.split('/').length; + const relativeParts = relativePath.split('/').slice(blockDirParts).join('/'); + const destinationPath = `${destinationDir}/${relativeParts}`; + + return `${distDir}/${destinationPath}`; }, noErrorOnMissing: true, }; @@ -126,23 +163,53 @@ function getCopyPluginConfigBlockAssetsPattern(rootDir, distDir, srcDirPattern = * * @param {string} rootDir - The root directory path. * @param {string} distDir - The distribution directory path. - * @param {string} context - The context specifying the type of files to copy ('images', 'fonts', 'block-json', 'render-php'). - * @param {string} from - The file matching pattern to copy. Defaults are provided within each pattern. + * @param {string} context - The context specifying the type of files to copy ('images', 'fonts', 'block-json', 'render-php', 'block-assets'). + * @param {string} srcDirPattern - The file matching pattern to copy. Defaults are provided within each pattern. + * @param {string} srcBlockDir - The directory containing blocks or extensions. + * @param {string} assetsDir - The directory to copy within each block. + * @param {string} distBlockDir - The destination directory to copy into. * @returns {Object} A CopyPlugin pattern configuration based on the provided context. * @throws {Error} If the context is unknown. */ -function enqueuesGetCopyPluginConfigPattern(rootDir, distDir, context, srcDirPattern) { +function enqueuesGetCopyPluginConfigPattern( + rootDir, + distDir, + context, + srcDirPattern, + srcBlockDir = 'block-editor/blocks', + assetsDir, + distBlockDir +) { switch (context) { case 'images': return getCopyPluginConfigImagePattern(rootDir, distDir, srcDirPattern); case 'fonts': return getCopyPluginConfigFontPattern(rootDir, distDir, srcDirPattern); case 'block-json': - return getCopyPluginConfigBlockJsonPattern(rootDir, distDir, srcDirPattern); + return getCopyPluginConfigBlockJsonPattern( + rootDir, + distDir, + srcDirPattern, + srcBlockDir, + distBlockDir + ); case 'render-php': - return getCopyPluginConfigRenderPhpPattern(rootDir, distDir, srcDirPattern); + return getCopyPluginConfigRenderPhpPattern( + rootDir, + distDir, + srcDirPattern, + srcBlockDir, + distBlockDir + ); case 'block-assets': - return getCopyPluginConfigBlockAssetsPattern(rootDir, distDir, srcDirPattern); + return getCopyPluginConfigBlockAssetsPattern( + rootDir, + distDir, + srcDirPattern, + srcBlockDir, + assetsDir, + distBlockDir + ); default: throw new Error(`Unknown context: ${context}`); } From 856bd1f79f9ab885b2e4ffa32106773e944f9155 Mon Sep 17 00:00:00 2001 From: Anthony Thorne Date: Wed, 4 Feb 2026 15:02:20 +0700 Subject: [PATCH 5/9] update docs for AU English and cache invalidation hooks --- docs/BLOCK-EDITOR.md | 50 +++++++++++++++++------------------ docs/FILTERS.md | 31 ++++++++++++---------- docs/THEME-ASSETS.md | 2 +- docs/WEBPACK-VS-WP-SCRIPTS.md | 26 +++++++++--------- docs/WEBPACK.md | 2 +- 5 files changed, 57 insertions(+), 54 deletions(-) diff --git a/docs/BLOCK-EDITOR.md b/docs/BLOCK-EDITOR.md index 4bc94c3..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 `