Skip to content

Instantly share code, notes, and snippets.

@kadamwhite
Last active October 8, 2023 19:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kadamwhite/55aeeb4918dd72044d2115b90658b53d to your computer and use it in GitHub Desktop.
Save kadamwhite/55aeeb4918dd72044d2115b90658b53d to your computer and use it in GitHub Desktop.
Block Hierarchy Tracker
<?php
/**
* Plugin Name: Track Rendering Context
* Plugin Author: K. Adam White
* Plugin Description: A system for tracking the rendering of blocks on the page.
*
* USAGE
*
* Activate plugin, then on any page on the site, add ?blocks_log to the URL.
* The block hierarchy will be rendered to the error log.
*
* Optionally, add &blocks_verbose as a second parameter to get more log detail.
*/
namespace Track_Rendering_Context;
use WP_Block;
function bootstrap() : void {
add_filter( 'pre_render_block', __NAMESPACE__ . '\\on_pre_render_block', 10, 3 );
add_filter( 'render_block', __NAMESPACE__ . '\\on_render_block', 100, 3 );
}
bootstrap();
class Block_Context {
/**
* Instance of this class for use as singleton
*
* @var static
*/
protected static $instance;
/**
* Array of blocks in rendering stack.
*
* @var array
*/
protected array $stack;
/**
* Initialize block context stack.
*/
public function __construct() {
$this->stack = [];
}
/**
* Get an instance of this class.
*
* @return static
*/
public static function get_instance() {
if ( ! is_a( static::$instance, __CLASS__ ) ) {
static::$instance = new static(); // @phpstan-ignore-line
}
return static::$instance;
}
/**
* Check whether a GET query parameter argument is present.
*
* @param string $flag
* @return boolean
*/
private static function has_flag( string $flag ) : bool {
return isset( $_GET[ $flag ] );
}
/**
* Return a readable breadcrumb list of the names of blocks in the current stack.
*
* @return string
*/
private function print_stack() : string {
// Map from blocks to block names.
return implode( ' > ', array_map(
function( $parsed_block ) {
return $parsed_block['blockName'] ?? 'unknown';
},
$this->stack
) );
}
/**
* Add a block to the stack.
*
* @param array $parsed_block Parsed block array.
* @return array
*/
private function add_block( array $parsed_block ) : array {
$this->stack[] = [
'blockName' => $parsed_block['blockName'],
'attrs' => $parsed_block['attrs'],
'innerBlocks' => count( $parsed_block['innerBlocks'] ?? [] ) ? implode( ', ', array_map(
function( $inner_block ) {
return $inner_block['blockName'];
},
$parsed_block['innerBlocks'] ?? []
) ) : 'none',
];
return $this->stack;
}
/**
* Pop a specific leaf block off the stack.
*
* Has no effect if the current leaf block does not match the passed block name.
*
* @param string $block_name
* @return array
*/
private function remove_block( string $block_name ) : array {
$current_leaf_block = $this->stack[ count( $this->stack ) - 1 ];
if ( $block_name === $current_leaf_block['blockName'] ?? 'unknown' ) {
array_pop( $this->stack );
}
return $this->stack;
}
/**
* Print the current stack to the error log, if logging is enabled.
*/
public static function log() : void {
if ( ! self::should_log() ) {
return;
}
if ( self::should_log_verbose() ) {
// Verbose mode gets the full attributes, as JSON.
error_log( wp_json_encode( self::get_instance()->stack, JSON_PRETTY_PRINT ) );
}
// Log with the `core/` prefixes removed for brevity.
error_log( 'Stack: ' . preg_replace(
'/core\//',
'',
self::get_instance()->print_stack()
) );
}
public static function should_log() {
return self::has_flag( 'blocks_log' );
}
/**
* Check whether verbose logging is in use (based on query args).
*
* @return boolean
*/
public static function should_log_verbose() {
return self::has_flag( 'blocks_verbose' );
}
/**
* Add a block to the stack.
*
* @param ?string $block_name
* @param array $parsed_block
* @return array Updated stack.
*/
public static function start_block( ?string $block_name, array $parsed_block ) : array {
if ( empty( $block_name ) ) {
return self::get_instance()->stack;
}
if ( self::should_log_verbose() ) {
error_log( "Starting block $block_name" );
}
return self::get_instance()->add_block( $parsed_block );
}
/**
* Remove a block from the stack.
*
* @param ?string $block
* @return array Updated stack.
*/
public static function end_block( ?string $block_name ) : array {
if ( empty( $block_name ) ) {
return self::get_instance()->stack;
}
if ( self::should_log_verbose() ) {
error_log( "Rendered block $block_name" );
}
return self::get_instance()->remove_block( $block_name ?? 'unknown' );
}
}
/**
* Filter before rendering a block.
*
* @param string|null $pre_render The pre-rendered content. Default null.
* @param array $parsed_block The block being rendered.
* @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
* @return string|null Unchanged $pre_render value.
*/
function on_pre_render_block( $pre_render, $parsed_block, $parent_block = null ) {
if ( empty( $parsed_block['blockName'] ) ) {
return $pre_render;
}
Block_Context::start_block( $parsed_block['blockName'], $parsed_block );
return $pre_render;
}
/**
* Run logic after rendering the block.
*
* @param string $block_content The block content.
* @param array $block The full block, including name and attributes.
* @param WP_Block $instance The block instance.
*/
function on_render_block( string $block_content, array $block, WP_Block $instance ) : string {
if ( empty( $block['blockName'] ) ) {
return $block_content;
}
Block_Context::log();
Block_Context::end_block( $block['blockName'] );
return $block_content;
}

Example output with ?blocks_log in the URL, on a single post with the latest WP default theme:

[08-Oct-2023 19:53:18 UTC] Stack: template-part > group > group > site-title
[08-Oct-2023 19:53:18 UTC] Stack: template-part > group > group > navigation
[08-Oct-2023 19:53:18 UTC] Stack: template-part > group > group > navigation
[08-Oct-2023 19:53:18 UTC] Stack: template-part > group > group
[08-Oct-2023 19:53:18 UTC] Stack: template-part > group
[08-Oct-2023 19:53:18 UTC] Stack: template-part
[08-Oct-2023 19:53:18 UTC] Stack: group > group > post-featured-image
[08-Oct-2023 19:53:18 UTC] Stack: group > group > post-title
[08-Oct-2023 19:53:18 UTC] Stack: group > group
[08-Oct-2023 19:53:18 UTC] Stack: group > post-content > paragraph
[08-Oct-2023 19:53:18 UTC] Stack: group > post-content
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > spacer
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > separator
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > paragraph
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > post-date
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > paragraph
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > post-terms
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > paragraph
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > post-author
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > paragraph
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group > post-terms
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column > group
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns > column
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > columns
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > heading
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comments-title
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column > avatar
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column > comment-author-name
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column > group > comment-date
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column > group > comment-edit-link
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column > group
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column > comment-content
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column > comment-reply-link
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns > column
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template > columns
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comment-template
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comments-pagination > comments-pagination-previous
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comments-pagination > comments-pagination-numbers
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comments-pagination > comments-pagination-next
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > comments-pagination
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments > post-comments-form
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group > comments
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern > group
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part > pattern
[08-Oct-2023 19:53:18 UTC] Stack: group > template-part
[08-Oct-2023 19:53:18 UTC] Stack: group
[08-Oct-2023 19:53:18 UTC] Stack: template-part > pattern > group > group > site-title
[08-Oct-2023 19:53:18 UTC] Stack: template-part > pattern > group > group > paragraph
[08-Oct-2023 19:53:18 UTC] Stack: template-part > pattern > group > group
[08-Oct-2023 19:53:18 UTC] Stack: template-part > pattern > group
[08-Oct-2023 19:53:18 UTC] Stack: template-part > pattern
[08-Oct-2023 19:53:18 UTC] Stack: template-part

Example output on the same page with ?blocks_log&blocks_verbose (an excerpt of the output, because it is verbose):

[08-Oct-2023 19:54:31 UTC] Stack: group > template-part > pattern > group > columns > column > group
[08-Oct-2023 19:54:31 UTC] Rendered block core/group
[08-Oct-2023 19:54:31 UTC] [
    {
        "blockName": "core\/group",
        "attrs": {
            "tagName": "main",
            "style": {
                "spacing": {
                    "margin": {
                        "top": "var:preset|spacing|50"
                    }
                }
            }
        },
        "innerBlocks": "core\/group, core\/post-content, core\/template-part, core\/template-part"
    },
    {
        "blockName": "core\/template-part",
        "attrs": {
            "slug": "post-meta",
            "theme": "twentytwentythree"
        },
        "innerBlocks": "none"
    },
    {
        "blockName": "core\/pattern",
        "attrs": {
            "slug": "twentytwentythree\/post-meta"
        },
        "innerBlocks": "none"
    },
    {
        "blockName": "core\/group",
        "attrs": {
            "style": {
                "spacing": {
                    "margin": {
                        "top": "var:preset|spacing|70"
                    }
                }
            },
            "layout": {
                "type": "constrained"
            }
        },
        "innerBlocks": "core\/separator, core\/columns"
    },
    {
        "blockName": "core\/columns",
        "attrs": {
            "align": "wide",
            "style": {
                "spacing": {
                    "margin": {
                        "top": "var:preset|spacing|30"
                    },
                    "blockGap": "var:preset|spacing|30"
                }
            },
            "fontSize": "small"
        },
        "innerBlocks": "core\/column, core\/column"
    },
    {
        "blockName": "core\/column",
        "attrs": {
            "style": {
                "spacing": {
                    "blockGap": "0px"
                }
            }
        },
        "innerBlocks": "core\/group, core\/group"
    }
]
[08-Oct-2023 19:54:31 UTC] Stack: group > template-part > pattern > group > columns > column
[08-Oct-2023 19:54:31 UTC] Rendered block core/column
[08-Oct-2023 19:54:31 UTC] Starting block core/column
[08-Oct-2023 19:54:31 UTC] Starting block core/group
[08-Oct-2023 19:54:31 UTC] Starting block core/paragraph
[08-Oct-2023 19:54:31 UTC] [
    {
        "blockName": "core\/group",
        "attrs": {
            "tagName": "main",
            "style": {
                "spacing": {
                    "margin": {
                        "top": "var:preset|spacing|50"
                    }
                }
            }
        },
        "innerBlocks": "core\/group, core\/post-content, core\/template-part, core\/template-part"
    },
    {
        "blockName": "core\/template-part",
        "attrs": {
            "slug": "post-meta",
            "theme": "twentytwentythree"
        },
        "innerBlocks": "none"
    },
    {
        "blockName": "core\/pattern",
        "attrs": {
            "slug": "twentytwentythree\/post-meta"
        },
        "innerBlocks": "none"
    },
    {
        "blockName": "core\/group",
        "attrs": {
            "style": {
                "spacing": {
                    "margin": {
                        "top": "var:preset|spacing|70"
                    }
                }
            },
            "layout": {
                "type": "constrained"
            }
        },
        "innerBlocks": "core\/separator, core\/columns"
    },
    {
        "blockName": "core\/columns",
        "attrs": {
            "align": "wide",
            "style": {
                "spacing": {
                    "margin": {
                        "top": "var:preset|spacing|30"
                    },
                    "blockGap": "var:preset|spacing|30"
                }
            },
            "fontSize": "small"
        },
        "innerBlocks": "core\/column, core\/column"
    },
    {
        "blockName": "core\/column",
        "attrs": {
            "style": {
                "spacing": {
                    "blockGap": "0px"
                }
            }
        },
        "innerBlocks": "core\/group"
    },
    {
        "blockName": "core\/group",
        "attrs": {
            "style": {
                "spacing": {
                    "blockGap": "0.5ch"
                }
            },
            "layout": {
                "type": "flex",
                "orientation": "vertical"
            }
        },
        "innerBlocks": "core\/paragraph, core\/post-terms"
    },
    {
        "blockName": "core\/paragraph",
        "attrs": [],
        "innerBlocks": "none"
    }
]
[08-Oct-2023 19:54:31 UTC] Stack: group > template-part > pattern > group > columns > column > group > paragraph
[08-Oct-2023 19:54:31 UTC] Rendered block core/paragraph
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment