Skip to content

Commit

Permalink
Global Styles: Load block CSS conditionally (#41160)
Browse files Browse the repository at this point in the history
* Global Styles: Load block styles as part of the block CSS

* Global Styles: Load block styles as part of the block CSS

* tidy up

* fix PHP linting

* add comments
  • Loading branch information
scruffian committed May 20, 2022
1 parent f352606 commit 323174a
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 0 deletions.
128 changes: 128 additions & 0 deletions lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

/**
* 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;
}
}
23 changes: 23 additions & 0 deletions lib/compat/wordpress-6.1/get-global-styles-and-settings.php
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 );
}
}
83 changes: 83 additions & 0 deletions lib/compat/wordpress-6.1/script-loader.php
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 ) {
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 ) {
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 );
2 changes: 2 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 323174a

Please sign in to comment.