From d016833ce2ba6760b0cf9064a27437aeaec3367e Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 19 May 2022 14:42:42 +0100 Subject: [PATCH 1/5] Global Styles: Load block styles as part of the block CSS --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 128 ++++++++++++++++++ lib/load.php | 2 + 2 files changed, 130 insertions(+) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index cea9bd77cf17e6..d23324c6ddd1b8 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -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. + * @param array $selectors List of selectors per block. + * @return array + */ + protected static function get_style_nodes( $theme_json, $selectors = array() ) { + $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 ) ); + + return apply_filters( 'gutenberg_get_style_nodes', $nodes ); + } + + /** + * 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 ) { + $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 ) { + $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; + } } diff --git a/lib/load.php b/lib/load.php index d38a4eca72af1e..5a613f94c91695 100644 --- a/lib/load.php +++ b/lib/load.php @@ -123,6 +123,8 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.1 compat. require __DIR__ . '/compat/wordpress-6.1/blocks.php'; require __DIR__ . '/compat/wordpress-6.1/persisted-preferences.php'; +require __DIR__ . '/compat/wordpress-6.1/get-global-styles-and-settings.php'; +require __DIR__ . '/compat/wordpress-6.1/script-loader.php'; require __DIR__ . '/compat/wordpress-6.1/class-wp-theme-json-6-1.php'; // Experimental features. From 933bc2a2081dccb33be2f8555f9f4d2faf868a90 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 19 May 2022 14:43:11 +0100 Subject: [PATCH 2/5] Global Styles: Load block styles as part of the block CSS --- .../get-global-styles-and-settings.php | 99 +++++++++++++++++++ lib/compat/wordpress-6.1/script-loader.php | 62 ++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 lib/compat/wordpress-6.1/get-global-styles-and-settings.php create mode 100644 lib/compat/wordpress-6.1/script-loader.php diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php new file mode 100644 index 00000000000000..a9f41156d25576 --- /dev/null +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -0,0 +1,99 @@ +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() { + $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'] ); + wp_add_inline_style( 'wp-block-' . $block_name, $block_css ); + } +} diff --git a/lib/compat/wordpress-6.1/script-loader.php b/lib/compat/wordpress-6.1/script-loader.php new file mode 100644 index 00000000000000..a46c8dcb88f35b --- /dev/null +++ b/lib/compat/wordpress-6.1/script-loader.php @@ -0,0 +1,62 @@ + Date: Thu, 19 May 2022 15:05:16 +0100 Subject: [PATCH 3/5] tidy up --- .../get-global-styles-and-settings.php | 79 ------------------- lib/compat/wordpress-6.1/script-loader.php | 4 +- 2 files changed, 2 insertions(+), 81 deletions(-) diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index a9f41156d25576..c5aee26df577f2 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -5,85 +5,6 @@ * @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() ) { - // 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(); - - $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. */ diff --git a/lib/compat/wordpress-6.1/script-loader.php b/lib/compat/wordpress-6.1/script-loader.php index a46c8dcb88f35b..1496a84af3e5ac 100644 --- a/lib/compat/wordpress-6.1/script-loader.php +++ b/lib/compat/wordpress-6.1/script-loader.php @@ -5,7 +5,7 @@ * @package gutenberg */ -function remove_block_nodes_from_theme_json( $nodes ) { +function filter_out_block_nodes( $nodes ) { return array_filter( $nodes, function( $node, $key ) { return ! in_array( 'blocks', $node['path'] ); }, ARRAY_FILTER_USE_BOTH ); @@ -37,7 +37,7 @@ function gutenberg_enqueue_global_styles() { } if ( $separate_assets ) { - add_filter( 'gutenberg_get_style_nodes', 'remove_block_nodes_from_theme_json' ); + add_filter( 'gutenberg_get_style_nodes', 'filter_out_block_nodes' ); // add each block as an inline css. wp_add_global_styles_for_blocks(); } From 9a95640d1c914c6f7a5ce1bf85771aba53aed705 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Fri, 20 May 2022 21:17:46 +0100 Subject: [PATCH 4/5] fix PHP linting --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 3 +-- lib/compat/wordpress-6.1/script-loader.php | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index d23324c6ddd1b8..efef63c3eeb413 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -107,10 +107,9 @@ protected static function get_blocks_metadata() { * @since 5.8.0 * * @param array $theme_json The tree to extract style nodes from. - * @param array $selectors List of selectors per block. * @return array */ - protected static function get_style_nodes( $theme_json, $selectors = array() ) { + protected static function get_style_nodes( $theme_json ) { $nodes = array(); if ( ! isset( $theme_json['styles'] ) ) { return $nodes; diff --git a/lib/compat/wordpress-6.1/script-loader.php b/lib/compat/wordpress-6.1/script-loader.php index 1496a84af3e5ac..4322cf36573d9e 100644 --- a/lib/compat/wordpress-6.1/script-loader.php +++ b/lib/compat/wordpress-6.1/script-loader.php @@ -5,10 +5,23 @@ * @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. + * + * @since 6.1 + * + * @param array $nodes The nodes to filter. + * @return array A filtered array of style nodes. + */ function filter_out_block_nodes( $nodes ) { - return array_filter( $nodes, function( $node, $key ) { - return ! in_array( 'blocks', $node['path'] ); - }, ARRAY_FILTER_USE_BOTH ); + return array_filter( + $nodes, + function( $node ) { + return ! in_array( 'blocks', $node['path'], true ); + }, + ARRAY_FILTER_USE_BOTH + ); } /** From 9475a8bea5402971d7e120dac00eb4d56c79fe9f Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Fri, 20 May 2022 21:38:56 +0100 Subject: [PATCH 5/5] add comments --- lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php | 1 + .../wordpress-6.1/get-global-styles-and-settings.php | 3 +++ lib/compat/wordpress-6.1/script-loader.php | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index efef63c3eeb413..f05ef80c7e2bad 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -137,6 +137,7 @@ protected static function get_style_nodes( $theme_json ) { $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 ); } diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index c5aee26df577f2..64d6a1050791b6 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -15,6 +15,9 @@ function wp_add_global_styles_for_blocks() { 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 ); } } diff --git a/lib/compat/wordpress-6.1/script-loader.php b/lib/compat/wordpress-6.1/script-loader.php index 4322cf36573d9e..d45a3a9344053b 100644 --- a/lib/compat/wordpress-6.1/script-loader.php +++ b/lib/compat/wordpress-6.1/script-loader.php @@ -9,6 +9,10 @@ * 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. @@ -49,6 +53,10 @@ function gutenberg_enqueue_global_styles() { 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 ) { add_filter( 'gutenberg_get_style_nodes', 'filter_out_block_nodes' ); // add each block as an inline css.