-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Global Styles: Load block CSS conditionally #41160
Changes from all commits
d016833
933bc2a
92ac01d
9a95640
9475a8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,4 +87,132 @@ protected static function get_blocks_metadata() { | |
|
||
return static::$blocks_metadata; | ||
} | ||
|
||
/** | ||
* Builds metadata for the style nodes, which returns in the form of: | ||
* | ||
* [ | ||
* [ | ||
* 'path' => [ 'path', 'to', 'some', 'node' ], | ||
* 'selector' => 'CSS selector for some node', | ||
* 'duotone' => 'CSS selector for duotone for some node' | ||
* ], | ||
* [ | ||
* 'path' => ['path', 'to', 'other', 'node' ], | ||
* 'selector' => 'CSS selector for other node', | ||
* 'duotone' => null | ||
* ], | ||
* ] | ||
* | ||
* @since 5.8.0 | ||
* | ||
* @param array $theme_json The tree to extract style nodes from. | ||
* @return array | ||
*/ | ||
protected static function get_style_nodes( $theme_json ) { | ||
$nodes = array(); | ||
if ( ! isset( $theme_json['styles'] ) ) { | ||
return $nodes; | ||
} | ||
|
||
// Top-level. | ||
$nodes[] = array( | ||
'path' => array( 'styles' ), | ||
'selector' => static::ROOT_BLOCK_SELECTOR, | ||
); | ||
|
||
if ( isset( $theme_json['styles']['elements'] ) ) { | ||
foreach ( $theme_json['styles']['elements'] as $element => $node ) { | ||
$nodes[] = array( | ||
'path' => array( 'styles', 'elements', $element ), | ||
'selector' => static::ELEMENTS[ $element ], | ||
); | ||
} | ||
} | ||
|
||
// Blocks. | ||
if ( ! isset( $theme_json['styles']['blocks'] ) ) { | ||
return $nodes; | ||
} | ||
|
||
$nodes = array_merge( $nodes, static::get_block_nodes( $theme_json ) ); | ||
|
||
// This filter allows us to modify the output of WP_Theme_JSON so that we can do things like loading block CSS independently. | ||
return apply_filters( 'gutenberg_get_style_nodes', $nodes ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is copied from the parent class except this line and the one above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we filter out |
||
} | ||
|
||
/** | ||
* A public helper to get the block nodes from a theme.json file. | ||
* | ||
* @return array The block nodes in theme.json. | ||
*/ | ||
public function get_styles_block_nodes() { | ||
return static::get_block_nodes( $this->theme_json ); | ||
} | ||
|
||
/** | ||
* An internal method to get the block nodes from a theme.json file. | ||
* | ||
* @param array $theme_json The theme.json converted to an array. | ||
* | ||
* @return array The block nodes in theme.json. | ||
*/ | ||
private static function get_block_nodes( $theme_json ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't new code, it's just extracted to a new function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is it extracted from? This PR doesn't delete any code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's in the definition of |
||
$selectors = static::get_blocks_metadata(); | ||
$nodes = array(); | ||
if ( ! isset( $theme_json['styles'] ) ) { | ||
return $nodes; | ||
} | ||
|
||
// Blocks. | ||
if ( ! isset( $theme_json['styles']['blocks'] ) ) { | ||
return $nodes; | ||
} | ||
|
||
foreach ( $theme_json['styles']['blocks'] as $name => $node ) { | ||
$selector = null; | ||
if ( isset( $selectors[ $name ]['selector'] ) ) { | ||
$selector = $selectors[ $name ]['selector']; | ||
} | ||
|
||
$duotone_selector = null; | ||
if ( isset( $selectors[ $name ]['duotone'] ) ) { | ||
$duotone_selector = $selectors[ $name ]['duotone']; | ||
} | ||
|
||
$nodes[] = array( | ||
'name' => $name, | ||
'path' => array( 'styles', 'blocks', $name ), | ||
'selector' => $selector, | ||
'duotone' => $duotone_selector, | ||
); | ||
|
||
if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { | ||
foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { | ||
$nodes[] = array( | ||
'path' => array( 'styles', 'blocks', $name, 'elements', $element ), | ||
'selector' => $selectors[ $name ]['elements'][ $element ], | ||
); | ||
} | ||
} | ||
} | ||
|
||
return $nodes; | ||
} | ||
|
||
/** | ||
* Gets the CSS rules for a particular block from theme.json. | ||
* | ||
* @param array $block_metadata Meta data about the block to get styles for. | ||
* | ||
* @return array Styles for the block. | ||
*/ | ||
public function get_styles_for_block( $block_metadata ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This new function gets styles for the given block. |
||
$node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); | ||
$selector = $block_metadata['selector']; | ||
$settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); | ||
$declarations = static::compute_style_properties( $node, $settings ); | ||
$block_rules = static::to_ruleset( $selector, $declarations ); | ||
return $block_rules; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
/** | ||
* API to interact with global settings & styles. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Adds global style rules to the inline style for each block. | ||
*/ | ||
function wp_add_global_styles_for_blocks() { | ||
$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); | ||
// TODO some nodes dont have a name... | ||
$block_nodes = $tree->get_styles_block_nodes(); | ||
foreach ( $block_nodes as $metadata ) { | ||
$block_css = $tree->get_styles_for_block( $metadata ); | ||
$block_name = str_replace( 'core/', '', $metadata['name'] ); | ||
// These block styles are added on block_render. | ||
// This hooks inline CSS to them so that they are loaded conditionally | ||
// based on whether or not the block is used on the page. | ||
wp_add_inline_style( 'wp-block-' . $block_name, $block_css ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Took me a while to figure out that the inline style exists at this stage and this only adds to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a comment to that effect here as it took me a while to figure out as well. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
/** | ||
* Load global styles assets in the front-end. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* This applies a filter to the list of style nodes that comes from `get_style_nodes` in WP_Theme_JSON. | ||
* This particular filter removes all of the blocks from the array. | ||
* | ||
* We want WP_Theme_JSON to be ignorant of the implementation details of how the CSS is being used. | ||
* This filter allows us to modify the output of WP_Theme_JSON depending on whether or not we are loading separate assets, | ||
* without making the class aware of that detail. | ||
* | ||
* @since 6.1 | ||
* | ||
* @param array $nodes The nodes to filter. | ||
* @return array A filtered array of style nodes. | ||
*/ | ||
function filter_out_block_nodes( $nodes ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is merged we won't need this anymore, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need it in a filter for the case where loading separate assets is disabled. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the script loader filters the output of the class that handles "processing of structures that adhere to the theme.json spec". Why not instantiate with a config? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The want the WP_Theme_Json class to be ignorant of the implementation details of how the CSS is being used - so passing a config which determines whether or not to include blocks goes against that principle. There's more context here: #40513 (review) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's leave a note in the code here so that it's immediately clear to future readers. |
||
return array_filter( | ||
$nodes, | ||
function( $node ) { | ||
return ! in_array( 'blocks', $node['path'], true ); | ||
}, | ||
ARRAY_FILTER_USE_BOTH | ||
); | ||
} | ||
|
||
/** | ||
* Enqueues the global styles defined via theme.json. | ||
* This should replace wp_enqueue_global_styles. | ||
* | ||
* @since 5.8.0 | ||
*/ | ||
function gutenberg_enqueue_global_styles() { | ||
$separate_assets = wp_should_load_separate_core_block_assets(); | ||
$is_block_theme = wp_is_block_theme(); | ||
$is_classic_theme = ! $is_block_theme; | ||
|
||
/* | ||
* Global styles should be printed in the head when loading all styles combined. | ||
* The footer should only be used to print global styles for classic themes with separate core assets enabled. | ||
* | ||
* See https://core.trac.wordpress.org/ticket/53494. | ||
*/ | ||
if ( | ||
( $is_block_theme && doing_action( 'wp_footer' ) ) || | ||
( $is_classic_theme && doing_action( 'wp_footer' ) && ! $separate_assets ) || | ||
( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $separate_assets ) | ||
) { | ||
return; | ||
} | ||
|
||
/** | ||
* If we are loading CSS for each block separately, then we can load the theme.json CSS conditionally. | ||
* This removes the CSS from the global-styles stylesheet and adds it to the inline CSS for each block. | ||
*/ | ||
if ( $separate_assets ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aside from this block, this whole function is copypasta. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a comment explaining this code branch. |
||
add_filter( 'gutenberg_get_style_nodes', 'filter_out_block_nodes' ); | ||
// add each block as an inline css. | ||
wp_add_global_styles_for_blocks(); | ||
} | ||
|
||
$stylesheet = gutenberg_get_global_stylesheet(); | ||
|
||
if ( empty( $stylesheet ) ) { | ||
return; | ||
} | ||
|
||
wp_register_style( 'global-styles', false, array(), true, true ); | ||
wp_add_inline_style( 'global-styles', $stylesheet ); | ||
wp_enqueue_style( 'global-styles' ); | ||
} | ||
|
||
remove_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ); | ||
remove_action( 'wp_footer', 'wp_enqueue_global_styles' ); | ||
remove_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_global_styles_assets' ); | ||
remove_action( 'wp_footer', 'gutenberg_enqueue_global_styles_assets' ); | ||
|
||
add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_global_styles' ); | ||
add_action( 'wp_footer', 'gutenberg_enqueue_global_styles', 1 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In unrelated testing in Core, I'm seeing notices coming from here:
@scruffian: can someone follow up here to not assume
$element
exists on the table? Otherwise this will happen whenever someone is using a recent theme with a less recent Core installation, for example.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've run into similar things a few times (here and here).
In Core, with Gutenberg activated, this line calls get_block_editor_settings() which calls
wp_get_global_stylesheet()
, and the subsequent chain of methods.So it looks like it's running the Core methods/classes which may not have the latest checks ?
I've added a check in Gutenberg:
I tried to find an "in-plugin" work around and discovered the following:
Removing the call to
WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()
in gutenberg_register_webfonts_from_theme_json removes the error. I don't know why 🤷The WP_Theme_JSON constructor is called twice where static:ELEMENTS refers to the Core const, that is, without the new elements
button
et. al., from the WP_Theme_JSON_Gutenberg class.This is why we see the error where theme.json contains unsupported elements.
If, in the constructor, we referred to
self::ELEMENTS
we'd avoid this error, but it'd also mean we'd have to overwrite the constructor every time ELEMENTS was updated.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like the way to go. Some copy pasta is a small price to pay for better consistency of how things work!
Also @ramonjd does #43622 solve @mcsf 's potential issue with recent themes/older core - starting WP 6.1?