diff --git a/lib/load.php b/lib/load.php index 57f7993440f57a..cb87af52afe010 100644 --- a/lib/load.php +++ b/lib/load.php @@ -122,6 +122,9 @@ function gutenberg_is_experiment_enabled( $name ) { if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php' ) ) { require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php'; } +if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-processor-gutenberg.php' ) ) { + require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-processor-gutenberg.php'; +} // Block supports overrides. require __DIR__ . '/block-supports/utils.php'; diff --git a/packages/style-engine/class-wp-style-engine-processor.php b/packages/style-engine/class-wp-style-engine-processor.php new file mode 100644 index 00000000000000..646ea43e7261a8 --- /dev/null +++ b/packages/style-engine/class-wp-style-engine-processor.php @@ -0,0 +1,93 @@ +store = $store; + } + + /** + * Get the CSS rules as a string. + * + * @return string The computed CSS. + */ + public function get_css() { + // Combine CSS selectors that have identical declarations. + $this->combine_rules_selectors(); + + // Build the CSS. + $css = ''; + $rules = $this->store->get_all_rules(); + foreach ( $rules as $rule ) { + $css .= $rule->get_css(); + } + return $css; + } + + /** + * Combines selectors from the rules store when they have the same styles. + * + * @return void + */ + private function combine_rules_selectors() { + $rules = $this->store->get_all_rules(); + + // Build an array of selectors along with the JSON-ified styles to make comparisons easier. + $selectors_json = array(); + foreach ( $rules as $selector => $rule ) { + $declarations = $rule->get_declarations()->get_declarations(); + ksort( $declarations ); + $selectors_json[ $selector ] = json_encode( $declarations ); + } + + // Combine selectors that have the same styles. + foreach ( $selectors_json as $selector => $json ) { + // Get selectors that use the same styles. + $duplicates = array_keys( $selectors_json, $json, true ); + // Skip if there are no duplicates. + if ( 1 >= count( $duplicates ) ) { + continue; + } + foreach ( $duplicates as $key ) { + // Unset the duplicates from the $selectors_json array to avoid looping through them as well. + unset( $selectors_json[ $key ] ); + // Remove the rules from the store. + $this->store->remove_rule( $key ); + } + // Create a new rule with the combined selectors. + $new_rule = $this->store->add_rule( implode( ',', $duplicates ) ); + // Set the declarations. The extra check is in place because `add_rule` in the store can return `null`. + if ( $new_rule ) { + $new_rule->add_declarations( $rules[ $selector ]->get_declarations() ); + } + } + } +} 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 new file mode 100644 index 00000000000000..8070024adf3133 --- /dev/null +++ b/packages/style-engine/phpunit/class-wp-style-engine-processor-test.php @@ -0,0 +1,127 @@ +add_rule( '.a-nice-rule' )->add_declarations( + array( + 'color' => 'var(--nice-color)', + 'background-color' => 'purple', + ) + ); + $a_nice_store->add_rule( '.a-nicer-rule' )->add_declarations( + array( + 'font-family' => 'Nice sans', + 'font-size' => '1em', + 'background-color' => 'purple', + ) + ); + + $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() ); + } + + /** + * Should merge CSS declarations. + */ + public function test_dedupe_and_merge_css_declarations() { + $an_excellent_store = WP_Style_Engine_CSS_Rules_Store_Gutenberg::get_store( 'excellent' ); + $an_excellent_renderer = new WP_Style_Engine_Processor_Gutenberg( $an_excellent_store ); + $an_excellent_store->add_rule( '.an-excellent-rule' )->add_declarations( + array( + 'color' => 'var(--excellent-color)', + 'border-style' => 'dotted', + ) + ); + $an_excellent_store->add_rule( '.an-excellent-rule' )->add_declarations( + array( + 'color' => 'var(--excellent-color)', + 'border-style' => 'dotted', + 'border-color' => 'brown', + ) + ); + + $this->assertEquals( '.an-excellent-rule {color: var(--excellent-color); border-style: dotted; border-color: brown;}', $an_excellent_renderer->get_css() ); + + $an_excellent_store->add_rule( '.an-excellent-rule' )->add_declarations( + array( + 'color' => 'var(--excellent-color)', + 'border-style' => 'dashed', + 'border-width' => '2px', + ) + ); + + $this->assertEquals( '.an-excellent-rule {color: var(--excellent-color); border-style: dashed; border-color: brown; border-width: 2px;}', $an_excellent_renderer->get_css() ); + } + + /** + * Should combine duplicate CSS rules. + */ + public function test_combine_css_rules() { + $a_sweet_store = WP_Style_Engine_CSS_Rules_Store_Gutenberg::get_store( 'sweet' ); + $a_sweet_renderer = new WP_Style_Engine_Processor_Gutenberg( $a_sweet_store ); + $a_sweet_store->add_rule( '.a-sweet-rule' )->add_declarations( + array( + 'color' => 'var(--sweet-color)', + 'background-color' => 'purple', + ) + ); + $a_sweet_store->add_rule( '#an-even-sweeter-rule > marquee' )->add_declarations( + array( + 'color' => 'var(--sweet-color)', + 'background-color' => 'purple', + ) + ); + + $this->assertEquals( '.a-sweet-rule,#an-even-sweeter-rule > marquee {color: var(--sweet-color); background-color: purple;}', $a_sweet_renderer->get_css() ); + } + + /** + * Should combine and store CSS rules. + */ + public function test_store_combined_css_rules() { + $a_lovely_store = WP_Style_Engine_CSS_Rules_Store_Gutenberg::get_store( 'lovely' ); + $a_lovely_renderer = new WP_Style_Engine_Processor_Gutenberg( $a_lovely_store ); + $a_lovely_store->add_rule( '.a-lovely-rule' )->add_declarations( + array( + 'border-color' => 'purple', + ) + ); + $a_lovely_store->add_rule( '.a-lovelier-rule' )->add_declarations( + array( + 'border-color' => 'purple', + ) + ); + + $this->assertEquals( '.a-lovely-rule,.a-lovelier-rule {border-color: purple;}', $a_lovely_renderer->get_css() ); + + $a_lovely_store->add_rule( '.a-most-lovely-rule' )->add_declarations( + array( + 'border-color' => 'purple', + ) + ); + $a_lovely_store->add_rule( '.a-perfectly-lovely-rule' )->add_declarations( + array( + 'border-color' => 'purple', + ) + ); + + $this->assertEquals( '.a-lovely-rule,.a-lovelier-rule,.a-most-lovely-rule,.a-perfectly-lovely-rule {border-color: purple;}', $a_lovely_renderer->get_css() ); + } +} diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index 2f6720d579452f..0b145fef2ebfd7 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -39,6 +39,7 @@ const bundledPackagesPhpConfig = [ 'WP_Style_Engine_CSS_Declarations', 'WP_Style_Engine_CSS_Rules_Store', 'WP_Style_Engine_CSS_Rule', + 'WP_Style_Engine_Processor', 'WP_Style_Engine', ], },