Skip to content

Commit

Permalink
Add initial version of the style engine (#37978)
Browse files Browse the repository at this point in the history
* Add initial version of the style engine

* Update Style type to move typography attributes to their own object

* Update useEngine check

* Clear out changelog

* Attempt to fix issue with package-lock.json and the check-local-changes step

* Switch getCSSRules to use camelCase and generate to switch to kebabCase

* Skip useEngine styles in globalstyles reduce call, update output to use kebabCase

* Change order of box sides to match TRBL order of shorthand syntax and fix unit test

* Add StyleOptions type and update function signature to include options object instead of selector string

* Remove unnecessary return statement

* Switch rules reassignment to rules.push

* Update style object types to use CSSProperties

Co-authored-by: Andrew Serong <[email protected]>
  • Loading branch information
youknowriad and andrewserong authored Feb 15, 2022
1 parent 8d56c4e commit d3688d8
Show file tree
Hide file tree
Showing 21 changed files with 459 additions and 16 deletions.
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,12 @@
"markdown_source": "../packages/shortcode/README.md",
"parent": "packages"
},
{
"title": "@wordpress/style-engine",
"slug": "packages-style-engine",
"markdown_source": "../packages/style-engine/README.md",
"parent": "packages"
},
{
"title": "@wordpress/stylelint-config",
"slug": "packages-stylelint-config",
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"@wordpress/rich-text": "file:packages/rich-text",
"@wordpress/server-side-render": "file:packages/server-side-render",
"@wordpress/shortcode": "file:packages/shortcode",
"@wordpress/style-engine": "file:packages/style-engine",
"@wordpress/token-list": "file:packages/token-list",
"@wordpress/url": "file:packages/url",
"@wordpress/viewport": "file:packages/viewport",
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@wordpress/notices": "file:../notices",
"@wordpress/rich-text": "file:../rich-text",
"@wordpress/shortcode": "file:../shortcode",
"@wordpress/style-engine": "file:../style-engine",
"@wordpress/token-list": "file:../token-list",
"@wordpress/url": "file:../url",
"@wordpress/warning": "file:../warning",
Expand Down
37 changes: 26 additions & 11 deletions packages/block-editor/src/hooks/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
__EXPERIMENTAL_ELEMENTS as ELEMENTS,
} from '@wordpress/blocks';
import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
import { getCSSRules } from '@wordpress/style-engine';

/**
* Internal dependencies
Expand Down Expand Up @@ -85,21 +86,35 @@ export function getInlineStyles( styles = {} ) {
// option and backwards compatibility for border radius support.
const styleValue = get( styles, path );

if ( !! subPaths && ! isString( styleValue ) ) {
Object.entries( subPaths ).forEach( ( entry ) => {
const [ name, subPath ] = entry;
const value = get( styleValue, [ subPath ] );

if ( value ) {
output[ name ] = compileStyleValue( value );
}
} );
} else if ( ! ignoredStyles.includes( path.join( '.' ) ) ) {
output[ propKey ] = compileStyleValue( get( styles, path ) );
if ( ! STYLE_PROPERTY[ propKey ].useEngine ) {
if ( !! subPaths && ! isString( styleValue ) ) {
Object.entries( subPaths ).forEach( ( entry ) => {
const [ name, subPath ] = entry;
const value = get( styleValue, [ subPath ] );

if ( value ) {
output[ name ] = compileStyleValue( value );
}
} );
} else if ( ! ignoredStyles.includes( path.join( '.' ) ) ) {
output[ propKey ] = compileStyleValue(
get( styles, path )
);
}
}
}
} );

// The goal is to move everything to server side generated engine styles
// This is temporary as we absorb more and more styles into the engine.
const extraRules = getCSSRules( styles, { selector: 'self' } );
extraRules.forEach( ( rule ) => {
if ( rule.selector !== 'self' ) {
throw "This style can't be added as inline style";
}
output[ rule.key ] = rule.value;
} );

return output;
}

Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/api/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = {
paddingBottom: 'bottom',
paddingLeft: 'left',
},
useEngine: true,
},
textDecoration: {
value: [ 'typography', 'textDecoration' ],
Expand Down
6 changes: 5 additions & 1 deletion packages/dependency-extraction-webpack-plugin/lib/util.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const WORDPRESS_NAMESPACE = '@wordpress/';
const BUNDLED_PACKAGES = [ '@wordpress/icons', '@wordpress/interface' ];
const BUNDLED_PACKAGES = [
'@wordpress/icons',
'@wordpress/interface',
'@wordpress/style-engine',
];

/**
* Default request to global transformation
Expand Down
1 change: 1 addition & 0 deletions packages/edit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@wordpress/notices": "file:../notices",
"@wordpress/plugins": "file:../plugins",
"@wordpress/reusable-blocks": "file:../reusable-blocks",
"@wordpress/style-engine": "file:../style-engine",
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
"classnames": "^2.3.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getBlockTypes,
} from '@wordpress/blocks';
import { useEffect, useState, useContext } from '@wordpress/element';
import { getCSSRules } from '@wordpress/style-engine';

/**
* Internal dependencies
Expand Down Expand Up @@ -146,11 +147,11 @@ function flattenTree( input = {}, prefix, token ) {
* @return {Array} An array of style declarations.
*/
function getStylesDeclarations( blockStyles = {} ) {
return reduce(
const output = reduce(
STYLE_PROPERTY,
( declarations, { value, properties }, key ) => {
( declarations, { value, properties, useEngine }, key ) => {
const pathToValue = value;
if ( first( pathToValue ) === 'elements' ) {
if ( first( pathToValue ) === 'elements' || useEngine ) {
return declarations;
}

Expand Down Expand Up @@ -188,6 +189,21 @@ function getStylesDeclarations( blockStyles = {} ) {
},
[]
);

// The goal is to move everything to server side generated engine styles
// This is temporary as we absorb more and more styles into the engine.
const extraRules = getCSSRules( blockStyles, { selector: 'self' } );
extraRules.forEach( ( rule ) => {
if ( rule.selector !== 'self' ) {
throw "This style can't be added as inline style";
}
const cssProperty = rule.key.startsWith( '--' )
? rule.key
: kebabCase( rule.key );
output.push( `${ cssProperty }: ${ compileStyleValue( rule.value ) }` );
} );

return output;
}

export const getNodesWithStyles = ( tree, blockSelectors ) => {
Expand Down
1 change: 1 addition & 0 deletions packages/style-engine/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
7 changes: 7 additions & 0 deletions packages/style-engine/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. -->

## Unreleased

### New Feature

- Added initial version of the style engine ([#37978](https://github.com/WordPress/gutenberg/pull/37978)).
62 changes: 62 additions & 0 deletions packages/style-engine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Style Engine

The Style Engine powering global styles and block customizations.

## Installation

Install the module

```bash
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._

## 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.

Currently it's not a package that generates a wp.styleEngine global because it's not ready yet, it's still a bundled package but ultimately, we want it to be so, once the roadmap is finished:

**TODO List:**

- Add style definitions for all the currently supported styles in blocks and theme.json.
- the CSS variable shortcuts for values (for presets...)
- Support generating styles in the frontend.
- Support generating styles in the backend (block supports and theme.json stylesheet).
- Refactor all block styles to use the style engine server side.
- Refactor all blocks to consistently use the "style" attribute for all customizations (get rid of the preset specific attributes).

## Usage

<!-- START TOKEN(Autogenerated API docs) -->

### generate

Generates a stylesheet for a given style object and selector.

_Parameters_

- _style_ `Style`: Style object.
- _options_ `StyleOptions`: Options object with settings to adjust how the styles are generated.

_Returns_

- `string`: generated stylesheet.

### getCSSRules

Returns a JSON representation of the generated CSS rules.

_Parameters_

- _style_ `Style`: Style object.
- _options_ `StyleOptions`: Options object with settings to adjust how the styles are generated.

_Returns_

- `GeneratedCSSRule[]`: generated styles.

<!-- END TOKEN(Autogenerated API docs) -->

<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
37 changes: 37 additions & 0 deletions packages/style-engine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@wordpress/style-engine",
"version": "0.1.0",
"description": "WordPress Style engine.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [
"wordpress",
"gutenberg",
"styles",
"global styles"
],
"homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/style-engine/README.md",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git",
"directory": "packages/style-engine"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
},
"engines": {
"node": ">=12"
},
"main": "build/index.js",
"module": "build-module/index.js",
"react-native": "src/index",
"types": "build-types",
"sideEffects": false,
"dependencies": {
"@babel/runtime": "^7.16.0",
"lodash": "^4.17.21"
},
"publishConfig": {
"access": "public"
}
}
64 changes: 64 additions & 0 deletions packages/style-engine/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* External dependencies
*/
import { groupBy, kebabCase } from 'lodash';

/**
* Internal dependencies
*/
import type {
Style,
StyleOptions,
GeneratedCSSRule,
StyleDefinition,
} from './types';
import { styleDefinitions } from './styles';

/**
* Generates a stylesheet for a given style object and selector.
*
* @param style Style object.
* @param options Options object with settings to adjust how the styles are generated.
*
* @return generated stylesheet.
*/
export function generate( style: Style, options: StyleOptions ): string {
const rules = getCSSRules( style, options );
const groupedRules = groupBy( rules, 'selector' );
const selectorRules = Object.keys( groupedRules ).reduce(
( acc: string[], subSelector: string ) => {
acc.push(
`${ subSelector } { ${ groupedRules[ subSelector ]
.map(
( rule: GeneratedCSSRule ) =>
`${ kebabCase( rule.key ) }: ${ rule.value };`
)
.join( ' ' ) } }`
);
return acc;
},
[]
);

return selectorRules.join( '\n' );
}

/**
* Returns a JSON representation of the generated CSS rules.
*
* @param style Style object.
* @param options Options object with settings to adjust how the styles are generated.
*
* @return generated styles.
*/
export function getCSSRules(
style: Style,
options: StyleOptions
): GeneratedCSSRule[] {
const rules: GeneratedCSSRule[] = [];
styleDefinitions.forEach( ( definition: StyleDefinition ) => {
rules.push( ...definition.generate( style, options ) );
} );

return rules;
}
6 changes: 6 additions & 0 deletions packages/style-engine/src/styles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Internal dependencies
*/
import padding from './padding';

export const styleDefinitions = [ padding ];
19 changes: 19 additions & 0 deletions packages/style-engine/src/styles/padding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Internal dependencies
*/
import type { Style, StyleOptions } from '../types';
import { generateBoxRules } from './utils';

const padding = {
name: 'padding',
generate: ( style: Style, options: StyleOptions ) => {
return generateBoxRules(
style,
options,
[ 'spacing', 'padding' ],
'padding'
);
},
};

export default padding;
Loading

0 comments on commit d3688d8

Please sign in to comment.