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: enqueue block supports styles in Gutenberg #42880

Merged
Merged
58 changes: 58 additions & 0 deletions lib/compat/wordpress-6.1/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,61 @@ static function () use ( $style ) {
);
}

/**
* Fetches, processes and compiles stored core styles, then combines and renders them to the page.
* Styles are stored via the style engine API. See: packages/style-engine/README.md
*/
function gutenberg_enqueue_stored_styles() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking, the idea with using a generic name like wp_enqueue_stored_styles rather than being tied to the style engine, is that we're not exposing any style engine behaviour here? I think that sounds good to me, in that this is about enqueuing stored styles in general, (and deals with essential styles for WordPress) rather than being specific to the style engine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another tricky naming issue 😄

"Stored" indirectly implies the style engine, but yeah the motivation was to expand this function later to apply to all stores. See comment above.

If that's true, it will probably require a strategy to subsume/integrate gutenberg_enqueue_global_styles() when we get to global styles. ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that we're not exposing any style engine behaviour here?

Only pulling from style engine stores. Do you think we should be more specific in the naming? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should be more specific in the naming? 🤔

Naming's hard! For anything that's specifically style engine related (rather than the style engine being an implementation detail), I like the idea of including style_engine in the public function name so that it's all grouped together. So I'd probably lean toward wp_style_engine_enqueue_stored_styles as the function name, but I don't feel at all strongly about it if you prefer the shorter name 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds infinitely reasonable.

I don't really have a preference either 🙉

I suppose a weak argument to keep it generic is that wp_style_engine_enqueue_stored_styles might communicate to some that it's a public function of the style engine package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose a weak argument to keep it generic is that wp_style_engine_enqueue_stored_styles might communicate to some that it's a public function of the style engine package.

Good point! Since neither of us feels strongly about it, I think that nudges us more toward keeping the shorter title — after all, it's a function defined outside of the style engine package.

$is_block_theme = wp_is_block_theme();
$is_classic_theme = ! $is_block_theme;

/*
* For block themes, print stored styles in the header.
* For classic themes, in the footer.
*/
if (
( $is_block_theme && doing_action( 'wp_footer' ) ) ||
( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) )
) {
return;
}

$core_styles_keys = array( 'block-supports' );
$compiled_core_stylesheet = '';
$style_tag_id = 'core';
foreach ( $core_styles_keys as $style_key ) {
// Add comment to identify core styles sections in debugging.
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
$compiled_core_stylesheet .= "/**\n * Core styles: $style_key\n */\n";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea to aid debugging!

}
// Chain core store ids to signify what the styles contain.
$style_tag_id .= '-' . $style_key;
$compiled_core_stylesheet .= gutenberg_style_engine_get_stylesheet_from_store( $style_key );
}

// Combine Core styles.
if ( ! empty( $compiled_core_stylesheet ) ) {
wp_register_style( $style_tag_id, false, array(), true, true );
wp_add_inline_style( $style_tag_id, $compiled_core_stylesheet );
wp_enqueue_style( $style_tag_id );
}

// If there are any other stores registered by themes etc, print them out.
$additional_stores = WP_Style_Engine_CSS_Rules_Store_Gutenberg::get_stores();
foreach ( array_keys( $additional_stores ) as $store_name ) {
if ( in_array( $store_name, $core_styles_keys, true ) ) {
continue;
}
$styles = gutenberg_style_engine_get_stylesheet_from_store( $store_name );
if ( ! empty( $styles ) ) {
$key = "wp-style-engine-$store_name";
wp_register_style( $key, false, array(), true, true );
wp_add_inline_style( $key, $styles );
wp_enqueue_style( $key );
}
}
}

/**
* 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.
Expand Down Expand Up @@ -109,5 +164,8 @@ function gutenberg_enqueue_global_styles() {
remove_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_global_styles_assets' );
remove_action( 'wp_footer', 'gutenberg_enqueue_global_styles_assets' );

// Enqueue global styles, and then block supports styles.
add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_global_styles' );
add_action( 'wp_footer', 'gutenberg_enqueue_global_styles', 1 );
add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_stored_styles' );
add_action( 'wp_footer', 'gutenberg_enqueue_stored_styles', 1 );
andrewserong marked this conversation as resolved.
Show resolved Hide resolved
166 changes: 162 additions & 4 deletions packages/style-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,158 @@

The Style Engine powering global styles and block customizations.

## Backend API

### wp_style_engine_get_styles()

Global public function to generate styles from a single style object, e.g., the value of
a [block's attributes.style object](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/#styles)
or
the [top level styles in theme.json](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)
.

_Parameters_

- _$block_styles_ `array` A block's `attributes.style` object or the top level styles in theme.json
- _$options_ `array<string|boolean>` An array of options to determine the output.
- _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 in a `style` tag on the site frontend.
- _convert_vars_to_classnames_ `boolean` Whether to skip converting CSS var:? values to var( --wp--preset--\* )
values. Default is `false`.
- _selector_ `string` When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`,
otherwise a concatenated string of properties and values.

_Returns_
`array<string|array>|null`

```php
array(
'css' => (string) A CSS ruleset or declarations block formatted to be placed in an HTML `style` attribute or tag.
'declarations' => (array) An array of property/value pairs representing parsed CSS declarations.
'classnames' => (string) Classnames separated by a space.
);
```

It will return compiled CSS declarations for inline styles, or, where a selector is provided, a complete CSS rule.

To enqueue a style for rendering in the site's frontend, the `$options` array requires the following:

1. **selector (string)** - this is the CSS selector for your block style CSS declarations.
2. **context (string)** - this tells the style engine where to store the styles. Styles in the same context will be
batched together and printed in the same HTML style tag. The default is `'block-supports'`.
3. **enqueue (boolean)** - tells the style engine to store the styles.

`wp_style_engine_get_styles` will return the compiled CSS and CSS declarations array.

#### Usage

```php
$block_styles = array(
'spacing' => array( 'padding' => '100px' )
);
$styles = wp_style_engine_get_styles(
$block_styles,
array(
'selector' => '.a-selector',
'context' => 'block-supports',
'enqueue' => true,
)
);
print_r( $styles );

/*
array(
'css' => '.a-selector{padding:10px}'
'declarations' => array( 'padding' => '100px' )
)
*/
```

### wp_style_engine_get_stylesheet_from_css_rules()

Use this function to compile and return a stylesheet for any CSS rules. The style engine will automatically merge declarations and combine selectors.

This function acts as a CSS compiler, but will also enqueue styles for rendering where `enqueue` and `context` strings are passed in the options.

_Parameters_

- _$css_rules_ `array<array>`
- _$options_ `array<string|boolean>` An array of options to determine the output.
- _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 store using the `context` value as a key.

_Returns_
`string` A compiled CSS string based on `$css_rules`.

#### Usage

```php
$styles = array(
array(
'selector'. => '.wp-pumpkin',
'declarations' => array( 'color' => 'orange' )
),
array(
'selector'. => '.wp-tomato',
'declarations' => array( 'color' => 'red' )
),
array(
'selector'. => '.wp-tomato',
'declarations' => array( 'padding' => '100px' )
),
array(
'selector'. => '.wp-kumquat',
'declarations' => array( 'color' => 'orange' )
),
);

$stylesheet = wp_style_engine_get_stylesheet_from_css_rules(
$styles,
array(
'context' => 'block-supports', // Indicates that these styles should be stored with block supports CSS.
'enqueue' => true, // Render the styles for output.
)
);
print_r( $stylesheet ); // .wp-pumpkin, .wp-kumquat {color:orange}.wp-tomato{color:red;padding:100px}
```

### wp_style_engine_get_stylesheet_from_store()

Returns compiled CSS from a store, if found.

_Parameters_

- _$store_key_ `string` An identifier describing the origin of the style object, e.g., 'block-supports' or ' global-styles'. Default is 'block-supports'.

_Returns_
`string` A compiled CSS string from the stored CSS rules.

#### Usage

```php
// First register some styles.
$styles = array(
array(
'selector'. => '.wp-apple',
'declarations' => array( 'color' => 'green' )
),
);

$stylesheet = wp_style_engine_get_stylesheet_from_css_rules(
$styles,
array(
'context' => 'fruit-styles',
'enqueue' => true,
)
);

// Later, fetch compiled rules from store.
$stylesheet = gutenberg_style_engine_get_stylesheet_from_store( 'fruit-styles' );
print_r( $stylesheet ); // .wp-apple{color:green;}
```

## Installation (JS only)

Install the module
Expand All @@ -10,13 +162,18 @@ Install the module
npm install @wordpress/style-engine --save
```

_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._
_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has
limited or no support for such language features and APIs, you should
include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill)
in your code._

## Important

This Package is considered experimental at the moment. The idea is to have a package used to generate styles based on a style object that is consistent between: backend, frontend, block style object and theme.json.
This Package is considered experimental at the moment. The idea is to have a package used to generate styles based on a
style object that is consistent between: backend, frontend, block style object and theme.json.

Because this package is experimental and still in development it does not yet generate a `wp.styleEngine` global. To get there, the following tasks need to be completed:
Because this package is experimental and still in development it does not yet generate a `wp.styleEngine` global. To get
there, the following tasks need to be completed:

**TODO List:**

Expand All @@ -26,7 +183,8 @@ Because this package is experimental and still in development it does not yet ge
- Support generating styles in the backend (block supports and theme.json stylesheet). (Ongoing)
- Refactor all block styles to use the style engine server side. (Ongoing)
- Consolidate global and block style rendering and enqueuing
- Refactor all blocks to consistently use the "style" attribute for all customizations (get rid of the preset specific attributes).
- Refactor all blocks to consistently use the "style" attribute for all customizations (get rid of the preset specific
attributes).

See [Tracking: Add a Style Engine to manage rendering block styles #38167](https://github.com/WordPress/gutenberg/issues/38167)

Expand Down
3 changes: 2 additions & 1 deletion packages/style-engine/class-wp-style-engine-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ private function combine_rules_selectors() {
unset( $this->css_rules[ $key ] );
}
// Create a new rule with the combined selectors.
$this->css_rules[ implode( ',', $duplicates ) ] = new WP_Style_Engine_CSS_Rule( implode( ',', $duplicates ), $declarations );
$duplicate_selectors = implode( ',', $duplicates );
$this->css_rules[ $duplicate_selectors ] = new WP_Style_Engine_CSS_Rule( $duplicate_selectors, $declarations );
}
}
}
Loading