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

Allow plugins to include a theme.json file in the same way themes do #41707

Closed
Aljullu opened this issue Jun 14, 2022 · 24 comments
Closed

Allow plugins to include a theme.json file in the same way themes do #41707

Aljullu opened this issue Jun 14, 2022 · 24 comments
Labels
[Feature] Extensibility The ability to extend blocks or the editing experience Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Task Issues or PRs that have been broken down into an individual action to take

Comments

@Aljullu
Copy link
Contributor

Aljullu commented Jun 14, 2022

Description

Currently, if a plugin wants to set default styles for its blocks, it can either write the styles in CSS or write them in the block default attributes.

None of these approaches allow themes to easily override the styles of the plugin using theme.json.

A solution would be to allow plugins to include a theme.json file in the same way themes are already doing it. Styles specified in the theme.json of a plugin, would have lower priority than the styles from the theme's theme.json and user styles.

This way, plugins can specify the default styles of their blocks in their theme.json and themes can override the styles from their theme.json.

Step-by-step reproduction instructions

Screenshots, screen recording, code snippet

No response

Environment info

No response

Please confirm that you have searched existing issues in the repo.

Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

Yes

@Aljullu Aljullu added [Feature] Extensibility The ability to extend blocks or the editing experience Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json labels Jun 14, 2022
@nerrad
Copy link
Contributor

nerrad commented Jun 14, 2022

Another example of how this will be useful (for a plugin like WooCommerce) is that the plugin provided theme.json could also provide an index of the blocks (and patterns) registered in the plugin.

I would suggest however that the hierarchy of properties be something like:

User > Block > Theme theme.json > Plugin theme.json

Potential implementation options

It looks like we would need to add some theme.json detection for plugins as a part of the WP_Theme_JSON_Resolver::get_merged_data function. There's a few routes (to kick start discussion) that could be taken here in terms of how the theme.json in a plugin would be discovered:

1. Filter for plugin to register theme.json path

The filter would likely be within the get_merged_data function.

2. Path defined via property in the plugin header

Similar to how other plugin details are set in the plugin header, there could be potentially a theme.json property where the relative path could be set for the theme.json (or even a flag that just indicates the plugin has one defined?).

One advantage of having this in the plugin header is that that is already parsed by WP so it could be an aid to discoverability (only hitting the filesystem for the file if needed). There's also some potential benefits for surfacing this more readily via the WP.org plugin listing (or other automations) as well.

3. Automatic discovery via specific location for file

Having a requirement that the theme.json for a plugin is at a specific path (eg. in the plugin root folder or theme/theme.json). During activation/update WordPress would automatically check to see if plugins have a theme.json and set the plugin slug to a registry for later reading/caching when WP_Theme_JSON_Resolver::get_merged_data is invoked.


Personally, I'm leaning more towards options 2 and 3 (perhaps a combination of both) because it still provides an opt-in path for plugins yet keeps implementation details handled by WordPress.

@mtias
Copy link
Member

mtias commented Jun 14, 2022

I think the plugin should come before the theme as a fallback and be merged into it, given the theme might want to style and control the plugin's blocks specifically, it should supercede if it's targeting those blocks.

Any suggestions for how the API might work from a plugin's perspective? The merging is fairly trivial, so it's more about the ergonomics. Consider also that WordPress bundles a theme.json as well for reference. There's been some requests to unload that bundled fallback before, so that also needs to be taken into account.

@nerrad
Copy link
Contributor

nerrad commented Jun 14, 2022

I think the plugin should come before the theme as a fallback and be merged into it

Agreed, what I listed in the hierarchy flow is intended to represent priority of control from left to right. So, user overrides blocks which override theme which override plugin (I may be wrong on the blocks -> theme order, I think that might have changed recently?)

Any suggestions for how the API might work from a plugin's perspective?

I'm not sure what specifically you're asking about here? I'm guessing this isn't a question about how the plugin theme.json would be surfaced to WordPress?

Consider also that WordPress bundles a theme.json as well for reference.

Yes, I see that, and the theme takes precedence over anything defined by core's theme.json. I think it would make sense to have the plugin sit between core and the theme in merging order.

There's been some requests to unload that bundled fallback before, so that also needs to be taken into account.

Unload as in remove it completely, or to have an interface for external control over unloading it?

@mtias
Copy link
Member

mtias commented Jun 14, 2022

Got it, was reading the sequence wrong. Unload as being able to remove it if you want to supply your own without any dependencies or fallbacks.

I'm asking how should a plugin register or make its json file available, assuming it'd exist as a separate file.

@nerrad
Copy link
Contributor

nerrad commented Jun 14, 2022

I'm asking how should a plugin register or make its json file available, assuming it'd exist as a separate file.

That's what I was addressing in my original comment. Ideally I think the plugin shouldn't need to explicitly register it other than maybe an option in the plugin header and then having the theme.json in a indicated path. So for instance maybe something like this for WooCommerce:

/**
 * Plugin Name: WooCommerce
 * Plugin URI: https://woocommerce.com/
 * Description: An eCommerce toolkit that helps you sell anything. Beautifully.
 * Version: 6.5.1
 * Author: Automattic
 * Author URI: https://woocommerce.com
 * Text Domain: woocommerce
 * Domain Path: /i18n/languages/
 * theme.json Path: /theme
 * Requires at least: 5.7
 * Requires PHP: 7.2
 *
 * @package WooCommerce
 */

The above would signal to WordPress core when parsing the plugin header on plugin activation/update that:

  • The plugin has a theme.json file.
  • It's located in the theme folder (relative to the plugin's root folder).

WordPress core could just register the paths for all plugins indicating they have a theme.json and then use that to load and merge when the initial resolution of all theme.json files happens on WP_Theme_JSON_Resolver::get_merged_data. Of course, we'd also have to account for when the resolution cache is cleared.

@luisherranz
Copy link
Member

I love the idea.

Two thoughts:

  1. I'd import it automatically if it exists. I don't see much value in making it opt-in when it's a new file that didn't exist previously.

  2. If we have block.json and theme.json, I'd call this one plugin.json. That way, it could have its own version and schema. And there may be settings from theme.json that won't make sense in a plugin and vice versa.

@gziolo
Copy link
Member

gziolo commented Jun 15, 2022

This is a fascinating idea. One additional consideration would be merging settings from multiple plugins.

Styles specified in the theme.json of a plugin, would have lower priority than the styles from the theme's theme.json and user styles.
This way, plugins can specify the default styles of their blocks in their theme.json and themes can override the styles from their theme.json.

With that, we would also need to figure out how to ensure that settings from multiple plugins are processed and merged in a predictable way.

If we have block.json and theme.json, I'd call this one plugin.json. That way, it could have its version and schema. And there may be settings from theme.json that won't make sense in a plugin and vice versa.

I don't have a formed opinion about the name of the file. I only wanted to surface potential challenges if we were to use translatable fields. We would have to create the list of translatable fields and implement the extraction of translations from plugin.json in WP CLI. I'm not sure how quickly we would run into that issue, but it's also worth emphasizing that we don't know how much the theme.json for plugins would overlap with theme.json from themes and how complex the merging strategy might become.

@nerrad
Copy link
Contributor

nerrad commented Jun 19, 2022

The latest comments have raised additional questions. I'm still thinking through this but wanted to at least document them here in case others have answers/thoughts. I definitely think we should keep the initial scope fairly limited, but some of the questions raised could influence the initial structure of this, hence I'm asking.

  • Should the plugin-provided properties only allow enhancing only a subset of potential theme.json properties? If so which? Keep in mind, theme.json would always override what is provided by the plugin.
  • Should this be named differently and allow for potential separate versioning and other potential future uses?
  • If multiple plugins provide the same properties, how do we handle merging them? Which plugin would "win" in any possible merge conflicts?
  • Do we implement the ability to completely unload the default merging strategy (and potentially replace it) as a part of work on this issue?

I'll leave a comment in this issue at some point once I've had a chance to think through the implications for various answers to the above questions.

@luisherranz
Copy link
Member

luisherranz commented Aug 4, 2022

It's not that a plugin's theme.json like the one described here would only serve to configure the default styles of its blocks, but as that was the use case Albert was referring to when he opened this issue, I think it'd be interesting if you could take a look at #41873. It's also trying to solve that problem, but by letting plugins define those default styles in their block.json file instead.

I'd love to hear what you think and how it overlaps with your needs here.

@nerrad
Copy link
Contributor

nerrad commented Aug 4, 2022

It's not that a plugin's theme.json like the one described here would only serve to configure the default styles of its blocks, but as that was the use case Albert was referring to when he opened this issue, I think it'd be interesting if you could take a look at #41873. It's also trying to solve that problem, but by letting plugins define those styles in their block.json file instead.

I'd love to hear what you think and how it overlaps with your needs here.

Thanks for sharing the link @luisherranz! I think the approach in #41873 is definitely interesting and especially that it keeps the default styles at the block level for blocks. If I'm understanding things correctly, that means if themes don't define any styles for specific blocks, then the registered default styles from the blocks are used. It also seems like this could potentially be useful for defining style variations at the block level here. This would also resolve the merging issue of multiple plugin-provided theme.json files that provide styles for the same blocks.

What is still unclear at this point is how that might solve for other potential use-cases around a plugin provided theme.json (that I outlined in the PR):

  • expose custom properties that can be overridden by themes.
  • provide and expose a set of custom templates for post types.
  • indicate what patterns to register from the Patterns directory, or potentially, automatically register patterns included by the plugin.

Granted, the above use cases aren't supported in the initial iteration of the PR and it's not clear if they would definitely be needed by plugins or if a plugin provided theme.json is the best mechanism for solving. The primary impetus behind these use-cases is that it would potentially allow a plugin a single mechanism for enhancing its support of all block themes without block themes having to explicitly opt-in to supporting multiple plugins. Rather than having to register that support via multiple different interfaces, theme.json would provide that single interface. Custom blocks and block.json is only one aspect of that type of support.

@oandregal
Copy link
Member

oandregal commented Aug 16, 2022

👋 I've take a look at this and see different goals:

  • To allow themes to easily override the styles of the plugin using theme.json

For this, I see #34180 as a more promising path. It gives a predictable hierarchy:

core (theme.json) > block (block.json) > theme (theme.json) > user (styles coming from the global styles sidebar)

Note that, unlike for plugins, all of the existing sources are unique (there's only a single WordPress install, a single block type, a single active theme, a single user preferences). Personally, for settings & styles, I see the existing path as more promising to investigate. Though we haven't done much work since that PR was merged. Perhaps an tracking issue listing and clarifying next steps for that API (trying new blocks, new styles, etc) would help people to be informed about the progress? cc @scruffian @adamziel @MaggieCabrera

  • Expose custom properties that can be overriden by themes.

We've talked about this a number of times and the conclusion was that if we allow exposing custom things, we won't be furthering the original goal of theme.json: provide consistency across themes & blocks to the user by leveraging a common set of styles & settings.

  • I'm not super familiar with the other goals, but perhaps we can draw inspiration from things that are working for themes already. For example, can we automatically register patterns stored under /patterns in the plugin's directory? etc.

@oandregal
Copy link
Member

On the topic of using theme.json for providing template metadata (titles, etc) I just remembered that there was some conversations about this and how counter-intuitive that felt. There was an exploration to place this metadata in the file itself at #38984

@nerrad
Copy link
Contributor

nerrad commented Aug 19, 2022

I've take a look at this and see different goals:

The primary problem we're trying to solve for here is for plugins (such as WooCommerce) to have the ability to define defaults for any blocks and/or templates it provides so that block themes don't have to explicitly support those plugins (but can still override). With #41873 now closed, what alternatives are being explored, besides what's proposed here?

I guess what you're proposing is that focus is redirected to something similar to what was in #41873, but that aligns more with what was started in #34180?

@oandregal
Copy link
Member

#41873 proposed to move the styles from one key to another and made that key stable. My concerns were that the initial work at #34180 had been partially reverted and no other blocks or styles were using the API yet. If the core blocks don't use the API how do we know it provides value to 3rd-party blocks?

I still think we should validate the approach started at #34180 by expanding its use and see how it performs.

As an analogy, after the initial explorations with block supports we laid out some next steps and found some limitations that we tracked at #28913 This helped anyone interested to learn about the API, understand its boundaries and help to move it forward. Perhaps if we do something similar that initial work will get more attention and momentum?

@gziolo gziolo mentioned this issue Aug 24, 2022
58 tasks
@gziolo
Copy link
Member

gziolo commented Aug 29, 2022

I spent some time investigating what the plugin author can expect when shipping custom blocks. This is what I tested using customised emptytheme from Gutenberg and a block scaffolded with npx @wordpress/create-block -t @wordpress/create-block-tutorial-template:

block/block.json

{
	"name": "woo/gutenpride",
	"supports": {
		"html": false,
		"__experimentalBorder": {
			"color": true,
			"radius": true,
			"style": true,
			"width": true
		},
		"typography": {
			"lineHeight": true
		}
	}
}

The default setting coming from WordPress Core in theme.json is set to:

https://github.com/WordPress/wordpress-develop/blob/1d456706549ccf4f0cc867b8a17d1dceaabf8a77/src/wp-includes/theme.json#L4-L10
https://github.com/WordPress/wordpress-develop/blob/1d456706549ccf4f0cc867b8a17d1dceaabf8a77/src/wp-includes/theme.json#L222

WP/wp-includes/theme.json

{
	"settings": {
		"appearanceTools": false,
		"border": {
			"color": false,
			"radius": false,
			"style": false,
			"width": false
		},
		"typography": {
			"lineHeight": false,
		}
	}
}

That means if the theme doesn't opt in to enable those settings for all blocks, then the plugin doesn't have a simple way to enable those border and typography controls in the UI.

In the WordPress Core there is an exception to some blocks to handle border differently:

https://github.com/WordPress/wordpress-develop/blob/1d456706549ccf4f0cc867b8a17d1dceaabf8a77/src/wp-includes/theme.json#L226-L240

WP/wp-includes/theme.json

{
	"settings": {
		"blocks": {
			"core/button": {
				"border": {
					"radius": true
				}
			},
			"core/pullquote": {
				"border": {
					"color": true,
					"radius": true,
					"style": true,
					"width": true
				}
			}
		}
	}
}

However, the challenge is that to update settings for this blocks, the theme needs to explicitly override the same setting using the same syntax with settings.blocks. It is also very unlikely that the theme would take into account custom blocks when defining the settings. In effect, even when the theme would like to disable all border controls with:

theme/theme.json

{
	"settings": {
		"border": {
			"color": false,
			"radius": false,
			"style": false,
			"width": false
		}
	}
}

It won't take effect for the core/button and core/pullquote. It's something that might be problematic when introducing theme.json for plugins because themes with the current resolution algorithm weren't able to override the defaults provided by plugins through blocks section.

@adamziel
Copy link
Contributor

adamziel commented Aug 29, 2022

The styles are determined using the following hierarchy:

  1. Core theme.json
  2. Current theme's theme.json
  3. User styles

One idea would be to add a few extension points to make it:

  1. Core theme.json
  2. Plugin-registered theme.json
  3. Current theme's theme.json
  4. Plugin-registered theme.json
  5. User styles
  6. Plugin-registered theme.json

@gziolo
Copy link
Member

gziolo commented Aug 29, 2022

I spent some time investigating what the plugin author can expect when shipping custom blocks. This is what I tested using customised emptytheme from Gutenberg and a block scaffolded with npx @wordpress/create-block -t @wordpress/create-block-tutorial-template:

My experiment continued. I removed some styles from the custom block so they don't override settings in JSON files. I added the following changes to the block.json file to check how (default?) styles get applied:

block/block.json:

{
	"name": "woo/gutenpride",
	"supports": {
		"html": false,
		"__experimentalBorder": {
			"color": true,
			"radius": true,
			"style": true,
			"width": true
		},
		"typography": {
			"lineHeight": true
		},
		"__experimentalStyle": {
			"typography": {
				"fontSize": "4em",
				"lineHeight": "4em"
			}
		}
	}
}

I also added similar styles in other places to find out how that hierarchy works.

theme/theme.json

{
	"styles": {
		"typography": {
			"fontSize": "3em",
			"lineHeight": "3em"
		},
		"blocks": {
			"woo/gutenpride": {
				"typography": {
					"fontSize": "3.5em",
					"lineHeight": "3.5em"
				}
			}
		}
	}
}

I also tweaked the theme.json in WordPress Core:

WP/theme.json

{
	"styles": {
		"typography": {
			"fontSize": "2em",
			"lineHeight": "2em"
		},
		"blocks": {
			"woo/gutenpride": {
				"typography": {
					"fontSize": "2.5em",
					"lineHeight": "2.5em"
				}
			}
		}
	}
}

Those styles get applied in the editor as follows:

Screen Shot 2022-08-30 at 09 20 05

Block-specific entry in the theme wins.

The same styles get applied on the front end a bit different:

Screen Shot 2022-08-30 at 09 21 53

Theme's general entry wins...


It looks like the order of importance in the editor resolves as follows:

theme theme.json:styles:blocks > block block.json:supports:__experimentalStyle > WordPress theme.json:styles:blocks > theme theme.json:styles > WordPress theme.json:styles

The styles are determined using the following hierarchy:

  1. Core theme.json
  2. Current theme's theme.json
  3. User styles

@adamziel, it's only partially true. It looks like its more nuanced when you define styles targeting individual blocks. They always take precedence over default styles defined in any theme.json. Well, at least it's true in the editor. It seems to work differently for custom static block types that implement save as follows:

export default function save( { attributes } ) {
	const blockProps = useBlockProps.save();
	return <div { ...blockProps }>{ attributes.message }</div>;
}

@luisherranz
Copy link
Member

In effect, even when the theme would like to disable all border controls with:
[...]
It won't take effect for the core/button and core/pullquote.

I think there's a difference between:

  • I want to change the configuration for any block that hasn't been explicitly configured.
  • I want to override the configuration of all the blocks, including those that have been explicitly configured.

I shared an idea about how that could be expressed here, but there could be other ways to do it:

@gziolo
Copy link
Member

gziolo commented Sep 1, 2022

I wanted to share a couple of thoughts based on my recent experiments. The __experimentalStyle thing might work for certain cases that @oandregal and @nerrad discussed, but I had difficulties to configure it correctly in my testing. This might be issue on my side or the API is still limited and requires further work. It definitely needs to be better documented so it's clear how to make the most of it.

There are other use cases discussed that can't be covered with __experimentalStyle defined for individual blocks in the block.json file. Let's take for example CSS custom properties:

{
	"version": 2,
	"settings": {
		"custom": {
			"line-height": {
				"body": 1.7,
				"heading": 1.3
			}
		}
	}
}

It’s something you don’t want to add to every block but add instead once for the custom functionality your plugin built to use it in the CSS provided. You could always define them inside the CSS manually, but it would be nice to let the Global Styles to handle it based on its resolution system.

Another example would Block styles defined once for multiple blocks:

"styles": {
	"typography": {
		"fontSize": "var(--wp--preset--font-size--normal)"
	}
}

Here, the idea from @luisherranz would fit perfectly:

"styles": {
	"blocks": {
		"woo/*": {
			"typography": {
				"fontSize": "var(--wp--preset--font-size--normal)"
			}
		}
	}
}

One final thought. I tend to agree with @adamziel that pointed out to me when we were discussing extending block.json with theme-related capability might be difficult to process. We have this mental model when thinking about styles:

  • theme has opinions about blocks, and can override it on the block level
  • now blocks would have opinions about the same things that theme owned before, so we need to learn what has the final saying and in which configuration

I think that I have a clear picture now of how the proposal explained by @Aljullu and @nerrad would benefit plugins like Woo that add the whole set of functionalities to the WordPress site. This way you can have default CSS variables, CSS styles, that you can use with custom blocks and templates in your plugin and they are subject to all rules that Global Styles introduce. In case of Woo, the additional benefit is that the plugin can offer default state for any block theme without block themes having to explicitly support Woo. However, they can opt in and reuse some CSS custom properties or presets whenever they want to. Themes can still optionally override that default state if they want.

@mtias
Copy link
Member

mtias commented Sep 10, 2022

#44015 is very relevant as it might allow plugins to effectively define its own theme.json by hooking and modifying. This is a lower level API, but it might allow a pathway to then formalizing registration of theme.json extensions.

@gziolo
Copy link
Member

gziolo commented Sep 16, 2022

#44015 is very relevant as it might allow plugins to effectively define its own theme.json by hooking and modifying. This is a lower level API, but it might allow a pathway to then formalizing registration of theme.json extensions.

Now, we landed filters for theme.json at different processing stages with #44015. Can we consider this issue resolved or do you think we should continue the process of improving the development experience with the initial idea to include theme.json file in the plugin?

@mtias
Copy link
Member

mtias commented Sep 16, 2022

I think we can consider this closed and ask plugins attempting to extend to provide feedback on what is working, what isn't, and what possible registration hooks might make sense to reduce boilerplate.

@mtias mtias closed this as completed Sep 16, 2022
@oandregal
Copy link
Member

Personally, for settings & styles, I see the existing path as more promising to investigate. Though we haven't done much work since that PR was merged. Perhaps an tracking issue listing and clarifying next steps for that API (trying new blocks, new styles, etc) would help people to be informed about the progress?

I have gone ahead and created #45198 to track the next steps for the __experimentalStyle API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Extensibility The ability to extend blocks or the editing experience Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Task Issues or PRs that have been broken down into an individual action to take
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants