-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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: Add Global Styles CSS to inline block CSS #40513
Changes from 4 commits
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 |
---|---|---|
@@ -0,0 +1,188 @@ | ||
<?php | ||
/** | ||
* WP_Theme_JSON_6_1 class | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Class that encapsulates the processing of structures that adhere to the theme.json spec. | ||
* | ||
* This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). | ||
* This is a low-level API that may need to do breaking changes. Please, | ||
* use get_global_settings, get_global_styles, and get_global_stylesheet instead. | ||
* | ||
* @access private | ||
*/ | ||
class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { | ||
/** | ||
* Returns the stylesheet that results of processing | ||
* the theme.json structure this object represents. | ||
* | ||
* @param array $types Types of styles to load. Will load all by default. It accepts: | ||
* 'variables': only the CSS Custom Properties for presets & custom ones. | ||
* 'styles': only the styles section in theme.json. | ||
* 'presets': only the classes for the presets. | ||
* @param array $origins A list of origins to include. By default it includes VALID_ORIGINS. | ||
* @return string Stylesheet. | ||
*/ | ||
public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null ) { | ||
if ( null === $origins ) { | ||
$origins = static::VALID_ORIGINS; | ||
} | ||
|
||
if ( is_string( $types ) ) { | ||
// Dispatch error and map old arguments to new ones. | ||
_deprecated_argument( __FUNCTION__, '5.9' ); | ||
if ( 'block_styles' === $types ) { | ||
$types = array( 'styles', 'presets' ); | ||
} elseif ( 'css_variables' === $types ) { | ||
$types = array( 'variables' ); | ||
} else { | ||
$types = array( 'variables', 'styles', 'presets' ); | ||
} | ||
} | ||
|
||
$blocks_metadata = static::get_blocks_metadata(); | ||
$separate_block_assets = wp_should_load_separate_core_block_assets(); | ||
$style_nodes_without_blocks = static::get_style_nodes( $this->theme_json, $blocks_metadata, $separate_block_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. Should be called 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. Or let the third argument do all the explaining? E.g., $should_exclude_block_nodes = wp_should_load_separate_core_block_assets();
$style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata, $should_exclude_block_nodes ); |
||
$setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); | ||
|
||
$stylesheet = ''; | ||
|
||
if ( in_array( 'variables', $types, true ) ) { | ||
$stylesheet .= $this->get_css_variables( $setting_nodes, $origins ); | ||
} | ||
|
||
if ( in_array( 'styles', $types, true ) ) { | ||
$stylesheet .= $this->get_block_classes( $style_nodes_without_blocks ); | ||
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 is really the only change to this function, plus the lines that define this variable. The purpose is to ensure that we are only adding top-level CSS to the Global Styles block, rather than block specific CSS which is added elsewhere. |
||
} | ||
|
||
if ( in_array( 'presets', $types, true ) ) { | ||
$stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); | ||
} | ||
|
||
return $stylesheet; | ||
} | ||
|
||
/** | ||
* 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. | ||
* @param array $selectors List of selectors per block. | ||
* @param array $exclude_blocks Exclude blocks from the style nodes. | ||
* @return array | ||
*/ | ||
protected static function get_style_nodes( $theme_json, $selectors = array(), $exclude_blocks = false ) { | ||
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 |
||
$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 ], | ||
); | ||
} | ||
} | ||
|
||
if ( $exclude_blocks ) { | ||
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 is the addition. |
||
return $nodes; | ||
} | ||
|
||
// Blocks. | ||
if ( ! isset( $theme_json['styles']['blocks'] ) ) { | ||
return $nodes; | ||
} | ||
|
||
$nodes = array_merge( $nodes, static::get_block_nodes_private( $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 is a refactor, moving this part of the code into a new function so it can be shared. |
||
|
||
return $nodes; | ||
} | ||
|
||
public function get_block_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 is a wrapper so that we can call this function as a method on the class as well as statically. I'm not sure this is a great idea. 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. Hmmm, yeah I see we use exclusively public functions for the API, e.g., I'm not sure there's an easy approach without a major refactor. |
||
return static::get_block_nodes_private( $this->theme_json ); | ||
} | ||
|
||
private static function get_block_nodes_private( $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 needs a better name! This isn't new code, it's just extracted from 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.
|
||
$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 is a new function! It gets the Global Styles CSS for one block, which can then be added to that block's inline CSS. |
||
$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,101 @@ | ||
<?php | ||
/** | ||
* API to interact with global settings & styles. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Returns the stylesheet resulting of merging core, theme, and user data. | ||
* This is a duplicate of wp_get_global_stylesheet | ||
* | ||
* @since 5.9.0 | ||
* | ||
* @param array $types Types of styles to load. Optional. | ||
* It accepts 'variables', 'styles', 'presets' as values. | ||
* If empty, it'll load all for themes with theme.json support | ||
* and only [ 'variables', 'presets' ] for themes without theme.json support. | ||
* | ||
* @return string Stylesheet. | ||
*/ | ||
function wp_get_global_stylesheet_gutenberg( $types = array() ) { | ||
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 almost a copy of what we have in core 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. Is this any different to I think the general approach is to copy that function over to the new compat file and keep the |
||
// Return cached value if it can be used and exists. | ||
// It's cached by theme to make sure that theme switching clears the cache. | ||
$can_use_cached = ( | ||
( empty( $types ) ) && | ||
( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && | ||
( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && | ||
( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && | ||
! is_admin() | ||
); | ||
$transient_name = 'global_styles_' . get_stylesheet(); | ||
if ( $can_use_cached ) { | ||
$cached = get_transient( $transient_name ); | ||
if ( $cached ) { | ||
return $cached; | ||
} | ||
} | ||
|
||
$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); | ||
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 (and the line below) are the only differences - we need to call the |
||
|
||
$supports_theme_json = WP_Theme_JSON_Resolver_Gutenberg::theme_has_support(); | ||
if ( empty( $types ) && ! $supports_theme_json ) { | ||
$types = array( 'variables', 'presets' ); | ||
} elseif ( empty( $types ) ) { | ||
$types = array( 'variables', 'styles', 'presets' ); | ||
} | ||
|
||
/* | ||
* If variables are part of the stylesheet, | ||
* we add them for all origins (default, theme, user). | ||
* This is so themes without a theme.json still work as before 5.9: | ||
* they can override the default presets. | ||
* See https://core.trac.wordpress.org/ticket/54782 | ||
*/ | ||
$styles_variables = ''; | ||
if ( in_array( 'variables', $types, true ) ) { | ||
$styles_variables = $tree->get_stylesheet( array( 'variables' ) ); | ||
$types = array_diff( $types, array( 'variables' ) ); | ||
} | ||
|
||
/* | ||
* For the remaining types (presets, styles), we do consider origins: | ||
* | ||
* - themes without theme.json: only the classes for the presets defined by core | ||
* - themes with theme.json: the presets and styles classes, both from core and the theme | ||
*/ | ||
$styles_rest = ''; | ||
if ( ! empty( $types ) ) { | ||
$origins = array( 'default', 'theme', 'custom' ); | ||
if ( ! $supports_theme_json ) { | ||
$origins = array( 'default' ); | ||
} | ||
$styles_rest = $tree->get_stylesheet( $types, $origins ); | ||
} | ||
|
||
$stylesheet = $styles_variables . $styles_rest; | ||
|
||
if ( $can_use_cached ) { | ||
// Cache for a minute. | ||
// This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. | ||
set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); | ||
} | ||
|
||
return $stylesheet; | ||
} | ||
|
||
/** | ||
* Adds global style rules to the inline style for each block. | ||
*/ | ||
function wp_add_global_styles_for_blocks() { | ||
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 is also a new function. It fetches the CSS for the block and adds it to the inline CSS block. |
||
$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); | ||
// TODO some nodes dont have a name... | ||
$block_nodes = $tree->get_block_nodes(); | ||
|
||
foreach ( $block_nodes as $metadata ) { | ||
$block_css = $tree->get_styles_for_block( $metadata ); | ||
$block_name = str_replace( 'core/', '', $metadata['name'] ); | ||
wp_add_inline_style( "wp-block-" . $block_name, $block_css ); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
/** | ||
* Enqueues the global styles defined via theme.json. | ||
* This should replace wp_enqueue_global_styles. | ||
* | ||
* @since 5.8.0 | ||
*/ | ||
function gutenberg_enqueue_global_styles() { | ||
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 is a copy of |
||
$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 ( $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. This is the only change - if we are loading separate block assets then we should include the Global Styles CSS in the block CSS rather than in the global-styles one. |
||
// add each block as an inline css. | ||
wp_add_global_styles_for_blocks(); | ||
} | ||
$stylesheet = wp_get_global_stylesheet_gutenberg(); | ||
|
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. We need to remove all the other times when we do this, so that we don't end up with many copies of the same styles. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?php | ||
/** | ||
* WP_Theme_JSON_6_1 class | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Class that encapsulates the processing of structures that adhere to the theme.json spec. | ||
* | ||
* This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). | ||
* This is a low-level API that may need to do breaking changes. Please, | ||
* use get_global_settings, get_global_styles, and get_global_stylesheet instead. | ||
* | ||
* @access private | ||
*/ | ||
class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_6_1 { | ||
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 enables us to always reference |
||
|
||
} |
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 think we should settle on a convention to only use the _Gutenberg suffix in the experimental folder, and all version specific classes should use the version suffix. That way if we want to inherit all the features in the plugin we don't need to specify the version of the class, we can just always use the _Gutenberg suffix. If we don't do this then we need to copy/paste even more code!