From 6004cb15911c0f9f2f1273d866a59a88ebee5cff Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 3 Aug 2022 14:56:46 +1000 Subject: [PATCH] Style engine: add optimize flag and combine functions into wp_style_engine_get_stylesheet (#42878) * Added an optimize flag to toggle combining selectors Adding tests Updating comments * Merged `wp_style_engine_get_stylesheet_from_css_rules()` and `wp_style_engine_add_to_store()` into `wp_style_engine_get_stylesheet()` * Default $options value to avoid "Too few arguments" warning in PHP unit tests * Rename wp_style_engine_get_stylesheet to wp_style_engine_get_stylesheet_from_css_rules Formatting tests for readability --- lib/block-supports/elements.php | 3 - lib/block-supports/layout.php | 10 ++- .../class-wp-style-engine-processor.php | 15 +++- .../style-engine/class-wp-style-engine.php | 68 +++++++----------- .../class-wp-style-engine-processor-test.php | 71 ++++++++++++++++--- .../phpunit/class-wp-style-engine-test.php | 35 +++++++-- 6 files changed, 138 insertions(+), 64 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 2a512eab82ca9..abcf8d340d3fc 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -92,9 +92,6 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { /* * For now we only care about link color. - * This code in the future when we have a public API - * should take advantage of WP_Theme_JSON_Gutenberg::compute_style_properties - * and work for any element and style. */ $skip_link_color_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'link' ); diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 8ae0ffc007fa9..21b7b9691b4ac 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -204,11 +204,15 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support if ( ! empty( $layout_styles ) ) { // Add to the style engine store to enqueue and render layout styles. - gutenberg_style_engine_add_to_store( 'layout-block-supports', $layout_styles ); - // Return compiled layout styles to retain backwards compatibility. // Since https://github.com/WordPress/gutenberg/pull/42452 we no longer call wp_enqueue_block_support_styles in this block supports file. - return gutenberg_style_engine_get_stylesheet_from_css_rules( $layout_styles ); + return gutenberg_style_engine_get_stylesheet_from_css_rules( + $layout_styles, + array( + 'context' => 'layout-block-supports', + 'enqueue' => true, + ) + ); } return ''; diff --git a/packages/style-engine/class-wp-style-engine-processor.php b/packages/style-engine/class-wp-style-engine-processor.php index 088513e83284d..e33c40e50abb8 100644 --- a/packages/style-engine/class-wp-style-engine-processor.php +++ b/packages/style-engine/class-wp-style-engine-processor.php @@ -63,16 +63,27 @@ public function add_rules( $css_rules ) { /** * Get the CSS rules as a string. * + * @param array $options array( + * 'optimize' => (boolean) Whether to optimize the CSS output, e.g., combine rules. + * );. + * * @return string The computed CSS. */ - public function get_css() { + public function get_css( $options = array() ) { + $defaults = array( + 'optimize' => true, + ); + $options = wp_parse_args( $options, $defaults ); + // If we have stores, get the rules from them. foreach ( $this->stores as $store ) { $this->add_rules( $store->get_all_rules() ); } // Combine CSS selectors that have identical declarations. - $this->combine_rules_selectors(); + if ( true === $options['optimize'] ) { + $this->combine_rules_selectors(); + } // Build the CSS. $css = ''; diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 33533fc872f69..944a3cc7872a0 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -623,15 +623,14 @@ function wp_style_engine_get_styles( $block_styles, $options = array() ) { if ( ! class_exists( 'WP_Style_Engine' ) ) { return array(); } - $defaults = array( + $defaults = array( 'selector' => null, 'context' => 'block-supports', 'convert_vars_to_classnames' => false, 'enqueue' => false, ); - - $style_engine = WP_Style_Engine::get_instance(); $options = wp_parse_args( $options, $defaults ); + $style_engine = WP_Style_Engine::get_instance(); $parsed_styles = null; // Block supports styles. @@ -661,64 +660,47 @@ function wp_style_engine_get_styles( $block_styles, $options = array() ) { return array_filter( $styles_output ); } -/** - * Global public interface method to register styles to be enqueued and rendered. - * - * @access public - * - * @param string $store_key A valid store key. - * @param array $css_rules array( - * 'selector' => (string) A CSS selector. - * 'declarations' => (boolean) An array of CSS definitions, e.g., array( "$property" => "$value" ). - * );. - * - * @return WP_Style_Engine_CSS_Rules_Store|null. - */ -function wp_style_engine_add_to_store( $store_key, $css_rules = array() ) { - if ( ! class_exists( 'WP_Style_Engine' ) || ! $store_key ) { - return null; - } - - // Get instance here to ensure that we register hooks to enqueue stored styles. - $style_engine = WP_Style_Engine::get_instance(); - - if ( empty( $css_rules ) ) { - return $style_engine::get_store( $store_key ); - } - - foreach ( $css_rules as $css_rule ) { - if ( empty( $css_rule['selector'] ) || empty( $css_rule['declarations'] ) ) { - continue; - } - $style_engine::store_css_rule( $store_key, $css_rule['selector'], $css_rule['declarations'] ); - } - return $style_engine::get_store( $store_key ); -} - /** * Returns compiled CSS from a collection of selectors and declarations. * This won't add to any store, but is useful for returning a compiled style sheet from any CSS selector + declarations combos. * * @access public * - * @param array $css_rules array( - * 'selector' => (string) A CSS selector. - * 'declarations' => (boolean) An array of CSS definitions, e.g., array( "$property" => "$value" ). + * @param array $css_rules array( + * array( + * 'selector' => (string) A CSS selector. + * declarations' => (boolean) An array of CSS definitions, e.g., array( "$property" => "$value" ). + * ) + * );. + * @param array $options array( + * 'context' => (string) An identifier describing the origin of the style object, e.g., 'block-supports' or 'global-styles'. Default is 'block-supports'. + * 'enqueue' => (boolean) When `true` will attempt to store and enqueue for rendering on the frontend. * );. * * @return string A compiled CSS string. */ -function wp_style_engine_get_stylesheet_from_css_rules( $css_rules = array() ) { - if ( ! class_exists( 'WP_Style_Engine' ) ) { +function wp_style_engine_get_stylesheet_from_css_rules( $css_rules, $options = array() ) { + if ( ! class_exists( 'WP_Style_Engine' ) || empty( $css_rules ) ) { return ''; } + $defaults = array( + 'context' => 'block-supports', + 'enqueue' => false, + ); + $options = wp_parse_args( $options, $defaults ); + $style_engine = WP_Style_Engine::get_instance(); $css_rule_objects = array(); foreach ( $css_rules as $css_rule ) { if ( empty( $css_rule['selector'] ) || empty( $css_rule['declarations'] ) || ! is_array( $css_rule['declarations'] ) ) { continue; } + + if ( true === $options['enqueue'] ) { + $style_engine::store_css_rule( $options['context'], $css_rule['selector'], $css_rule['declarations'] ); + } + $css_rule_objects[] = new WP_Style_Engine_CSS_Rule( $css_rule['selector'], $css_rule['declarations'] ); } @@ -726,5 +708,5 @@ function wp_style_engine_get_stylesheet_from_css_rules( $css_rules = array() ) { return ''; } - return WP_Style_Engine::compile_stylesheet_from_css_rules( $css_rule_objects ); + return $style_engine::compile_stylesheet_from_css_rules( $css_rule_objects ); } diff --git a/packages/style-engine/phpunit/class-wp-style-engine-processor-test.php b/packages/style-engine/phpunit/class-wp-style-engine-processor-test.php index ebee00f05ce16..af30639013607 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-processor-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-processor-test.php @@ -35,14 +35,17 @@ public function test_return_rules_as_css() { ); $a_nice_processor = new WP_Style_Engine_Processor(); $a_nice_processor->add_rules( array( $a_nice_css_rule, $a_nicer_css_rule ) ); - $this->assertEquals( '.a-nice-rule {color: var(--nice-color); background-color: purple;}.a-nicer-rule {font-family: Nice sans; font-size: 1em; background-color: purple;}', $a_nice_processor->get_css() ); + $this->assertEquals( + '.a-nice-rule {color: var(--nice-color); background-color: purple;}.a-nicer-rule {font-family: Nice sans; font-size: 1em; background-color: purple;}', + $a_nice_processor->get_css() + ); } /** * Should compile CSS rules from the store. */ public function test_return_store_rules_as_css() { - $a_nice_store = WP_Style_Engine_CSS_Rules_Store_Gutenberg::get_store( 'nice' ); + $a_nice_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'nice' ); $a_nice_store->add_rule( '.a-nice-rule' )->add_declarations( array( 'color' => 'var(--nice-color)', @@ -56,9 +59,12 @@ public function test_return_store_rules_as_css() { 'background-color' => 'purple', ) ); - $a_nice_renderer = new WP_Style_Engine_Processor_Gutenberg(); + $a_nice_renderer = new WP_Style_Engine_Processor(); $a_nice_renderer->add_store( $a_nice_store ); - $this->assertEquals( '.a-nice-rule {color: var(--nice-color); background-color: purple;}.a-nicer-rule {font-family: Nice sans; font-size: 1em; background-color: purple;}', $a_nice_renderer->get_css() ); + $this->assertEquals( + '.a-nice-rule {color: var(--nice-color); background-color: purple;}.a-nicer-rule {font-family: Nice sans; font-size: 1em; background-color: purple;}', + $a_nice_renderer->get_css() + ); } /** @@ -84,7 +90,10 @@ public function test_dedupe_and_merge_css_declarations() { ) ); $an_excellent_processor->add_rules( $another_excellent_rule ); - $this->assertEquals( '.an-excellent-rule {color: var(--excellent-color); border-style: dotted; border-color: brown;}', $an_excellent_processor->get_css() ); + $this->assertEquals( + '.an-excellent-rule {color: var(--excellent-color); border-style: dotted; border-color: brown;}', + $an_excellent_processor->get_css() + ); $yet_another_excellent_rule = new WP_Style_Engine_CSS_Rule( '.an-excellent-rule' ); $yet_another_excellent_rule->add_declarations( @@ -95,7 +104,47 @@ public function test_dedupe_and_merge_css_declarations() { ) ); $an_excellent_processor->add_rules( $yet_another_excellent_rule ); - $this->assertEquals( '.an-excellent-rule {color: var(--excellent-color); border-style: dashed; border-color: brown; border-width: 2px;}', $an_excellent_processor->get_css() ); + $this->assertEquals( + '.an-excellent-rule {color: var(--excellent-color); border-style: dashed; border-color: brown; border-width: 2px;}', + $an_excellent_processor->get_css() + ); + } + + /** + * Should print out uncombined selectors duplicate CSS rules. + */ + public function test_output_verbose_css_rules() { + $a_sweet_rule = new WP_Style_Engine_CSS_Rule( + '.a-sweet-rule', + array( + 'color' => 'var(--sweet-color)', + 'background-color' => 'purple', + ) + ); + + $a_sweeter_rule = new WP_Style_Engine_CSS_Rule( + '#an-even-sweeter-rule > marquee', + array( + 'color' => 'var(--sweet-color)', + 'background-color' => 'purple', + ) + ); + + $the_sweetest_rule = new WP_Style_Engine_CSS_Rule( + '.the-sweetest-rule-of-all a', + array( + 'color' => 'var(--sweet-color)', + 'background-color' => 'purple', + ) + ); + + $a_sweet_processor = new WP_Style_Engine_Processor(); + $a_sweet_processor->add_rules( array( $a_sweet_rule, $a_sweeter_rule, $the_sweetest_rule ) ); + + $this->assertEquals( + '.a-sweet-rule {color: var(--sweet-color); background-color: purple;}#an-even-sweeter-rule > marquee {color: var(--sweet-color); background-color: purple;}.the-sweetest-rule-of-all a {color: var(--sweet-color); background-color: purple;}', + $a_sweet_processor->get_css( array( 'optimize' => false ) ) + ); } /** @@ -121,7 +170,10 @@ public function test_combine_css_rules() { $a_sweet_processor = new WP_Style_Engine_Processor(); $a_sweet_processor->add_rules( array( $a_sweet_rule, $a_sweeter_rule ) ); - $this->assertEquals( '.a-sweet-rule,#an-even-sweeter-rule > marquee {color: var(--sweet-color); background-color: purple;}', $a_sweet_processor->get_css() ); + $this->assertEquals( + '.a-sweet-rule,#an-even-sweeter-rule > marquee {color: var(--sweet-color); background-color: purple;}', + $a_sweet_processor->get_css() + ); } /** * Should combine and store CSS rules. @@ -160,6 +212,9 @@ public function test_combine_previously_added_css_rules() { ); $a_lovely_processor->add_rules( $a_perfectly_lovely_rule ); - $this->assertEquals( '.a-lovely-rule,.a-lovelier-rule,.a-most-lovely-rule,.a-perfectly-lovely-rule {border-color: purple;}', $a_lovely_processor->get_css() ); + $this->assertEquals( + '.a-lovely-rule,.a-lovelier-rule,.a-most-lovely-rule,.a-perfectly-lovely-rule {border-color: purple;}', + $a_lovely_processor->get_css() + ); } } diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index 14938e075fa26..9c3ca271faa3d 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -540,14 +540,39 @@ public function test_enqueue_block_styles_store() { * Tests adding rules to a store and retrieving a generated stylesheet. */ public function test_add_to_store() { - $store = wp_style_engine_add_to_store( 'test-store', array() ); - - // wp_style_engine_add_to_store returns a store object. - $this->assertInstanceOf( 'WP_Style_Engine_CSS_Rules_Store', $store ); + $css_rules = array( + array( + 'selector' => '.frodo', + 'declarations' => array( + 'color' => 'brown', + 'height' => '10px', + 'width' => '30px', + 'border-style' => 'dotted', + ), + ), + array( + 'selector' => '.samwise', + 'declarations' => array( + 'color' => 'brown', + 'height' => '20px', + 'width' => '50px', + 'border-style' => 'solid', + ), + ), + ); + $compiled_stylesheet = wp_style_engine_get_stylesheet_from_css_rules( + $css_rules, + array( + 'context' => 'test-store', + 'enqueue' => true, + ) + ); // Check that the style engine knows about the store. - $stored_store = WP_Style_Engine::get_instance()::get_store( 'test-store' ); + $style_engine = WP_Style_Engine::get_instance(); + $stored_store = $style_engine::get_store( 'test-store' ); $this->assertInstanceOf( 'WP_Style_Engine_CSS_Rules_Store', $stored_store ); + $this->assertSame( $compiled_stylesheet, $style_engine::compile_stylesheet_from_css_rules( $stored_store->get_all_rules() ) ); } /**