Skip to content
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

Style Engine: Add a WP_Style_Engine_Processor object #42463

Merged
merged 11 commits into from
Jul 19, 2022
3 changes: 3 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
93 changes: 93 additions & 0 deletions packages/style-engine/class-wp-style-engine-processor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php
/**
* WP_Style_Engine_Processor
*
* Compiles styles from a store of CSS rules.
*
* @package Gutenberg
*/

if ( class_exists( 'WP_Style_Engine_Processor' ) ) {
return;
}

/**
* Compiles styles from a store of CSS rules.
*
* @access private
*/
class WP_Style_Engine_Processor {

/**
* The Style-Engine Store object.
*
* @var WP_Style_Engine_CSS_Rules_Store
*/
protected $store;

/**
* Constructor.
*
* @param WP_Style_Engine_CSS_Rules_Store $store The store to render.
*/
public function __construct( WP_Style_Engine_CSS_Rules_Store $store ) {
$this->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() );
}
}
}
}
127 changes: 127 additions & 0 deletions packages/style-engine/phpunit/class-wp-style-engine-processor-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php
/**
* Tests the Style Engine Renderer class.
*
* @package Gutenberg
* @subpackage style-engine
*/
require __DIR__ . '/../class-wp-style-engine-css-rules-store.php';
require __DIR__ . '/../class-wp-style-engine-css-rule.php';
require __DIR__ . '/../class-wp-style-engine-css-declarations.php';
require __DIR__ . '/../class-wp-style-engine-processor.php';

/**
* Tests for compiling and rendering styles from a store of CSS rules.
*/
class WP_Style_Engine_Processor_Test extends WP_UnitTestCase {
/**
* 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_renderer = new WP_Style_Engine_Processor_Gutenberg( $a_nice_store );
$a_nice_store->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() );
}
}
1 change: 1 addition & 0 deletions tools/webpack/packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
},
Expand Down