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

Create a mechanism to expose unstable APIs to plugins #66197

Open
jsnajdr opened this issue Oct 17, 2024 · 43 comments
Open

Create a mechanism to expose unstable APIs to plugins #66197

jsnajdr opened this issue Oct 17, 2024 · 43 comments
Assignees
Labels
[Feature] Extensibility The ability to extend blocks or the editing experience [Package] Private APIs /packages/private-apis [Type] Enhancement A suggestion for improvement.

Comments

@jsnajdr
Copy link
Member

jsnajdr commented Oct 17, 2024

The current Gutenberg policy, aligned with WordPress backward compatibility policy, is to not expose any private APIs to plugins at all. When a Gutenberg package exposes a private API, it's supposed to be used only by another Gutenberg package. For example, the Site Editor can use a private API from Block Editor or Components.

The packages try very hard to prevent plugins from using private API, there is the lock/unlock API and also @peterwilsoncc is hardening it by regularly changing the consent string in PRs like #55182.

However, this is a situation that treats the unstable APIs as "static" -- they already are here, and sometimes we review them and promote them to stable APIs. But I think we're not paying enough attention to the "dynamic" aspect, how new APIs are created and evolved before they are stabilized.

Currently, the only way how a new API can be reasonably created and evolved is when some Gutenberg app needs it. Because the Gutenberg packages can be broadly divided into "libraries" and "apps": libraries provide components, frameworks and utilities, and the apps (Post Editor, Site Editor, the 100+ core blocks) use them. If one of the apps needs something new from the libraries, we create a private API for that. That API can be modified as we learn more about the use cases and add new ones. Sometimes we stabilize an API and make it public, available to plugins.

The downside of this is that a plugin author never has a chance to participate in the development! They can use only something that is already finished. There's no way how a plugin author can experiment with private APIs and provide feedback, and participate in their evolution. Only the Gutenberg apps and blocks can be part of this process of trial and error.

This is now becoming a problem as we're working on new projects that go beyond Gutenberg, trying to rethink and refresh the entire wp-admin experience. The WooCommerce project would like to be part of the team, to use the new components like DataView or DataForm and rebuild the admin experience with them. Their use cases are more complex and rich than what is in Core and Gutenberg, we need them to verify that we're doing the Core APIs right.

So, can we find a way to allow plugins to participate in the evolution of new APIs, while at the same time keeping the WordPress backward compatibility contract?

One source of inspiration could be the JavaScript language and the TC39 process. After a new feature is added to the language standard, it needs to be finished and it's going to be there forever. But how is the feature developed and evolved?

Their answer is that potential new APIs go through several stages, stage 0 to stage 4. The stage signals how stable the feature is and how much you can rely on it. Developers can enable the unstable features in their build tools (the Babel transpiler etc.) or use polyfills. Browsers implement unstable APIs behind feature flags, and expose them unflagged in development versions (Google Canary, Firefox Nightly). That provides opportunities for testing, for developers to try them out and provide feedback.

Could WordPress have something similar?

@jsnajdr jsnajdr added the [Type] Enhancement A suggestion for improvement. label Oct 17, 2024
@colorful-tones
Copy link
Member

This sounds like the means for a great Make proposal.

@tyxla
Copy link
Member

tyxla commented Oct 17, 2024

This might be worth feedback from a broader audience: @WordPress/gutenberg-core

@peterwilsoncc peterwilsoncc added the [Package] Private APIs /packages/private-apis label Oct 17, 2024
@peterwilsoncc
Copy link
Contributor

The background for the introduction of the private APIs in place of __unstable and __experimental APIs was due to their use in themes and plugins rendering them as faux-stable. Changing them would break many sites so WordPress became stuck with the initial API design, even though it was later discovered to be sub-optimal.

My concern with allowing plugin to opt-in to the private APIs is that WordPress would end up in a similar situation again. I worry that we'd end up in a situation in which WordPress couldn't unlock an API for risk of breaking sites (@adamziel worked on the code, so this may not be an issue -- I suggested it & try to track string changes each release).

__dangerousOptInToUnstableAPIsOnlyForCoreModules() does allow for plugins to op-in to private APIs while running the plugin, although the string needs to be maintained by plugins doing so.

// The safety measure is meant for WordPress core where IS_WORDPRESS_CORE is set to true.
const allowReRegistration = globalThis.IS_WORDPRESS_CORE ? false : true;

@jsnajdr is your request to allow opt-in to be easier only when running the Gutenberg plugin, or do you wish to be able to opt in for sites running WordPress Core?

@youknowriad
Copy link
Contributor

I feel this need everyday when developing on Gutenberg. For a long term and sustainable project, a way to have a feedback loop for APIs, just like we do for features is needed. I know it's something that WordPress never had (aside from a small period where we had the experimental APIs in Gutenberg). I don't have a solution, but it's definitely a problem that is worth solving if we want to be shipping the right stable APIs.

@draganescu
Copy link
Contributor

But isn't the consent string a way to opt in?

@youknowriad
Copy link
Contributor

@draganescu Right now, the consent string can only be used by Core packages, not third-party packages. At least, that's the theory, they can pretend that they're a core package and use it but that's a hack really and can break anytime if the core package in question start loading in the page.

@draganescu
Copy link
Contributor

Then the 1st thing that I can think of is to properly define the difference between opting into an API to participate into its formation vs relying on an unstable API.

@talldan
Copy link
Contributor

talldan commented Oct 18, 2024

A way to statically anyalyze the usage of the private APIs would also be good. For most of the experimental APIs we've resorted to using https://www.wpdirectory.net/ to search whether plugins are using an API, but it's not perfect, it can overmatch or be hard to find exact matches.

If there were a manifest or something that plugins had to use to say which private APIs they're using it'd be much easier to search for usage and even notify them when we're removing a private API.

Not sure how this would work, part of the problem is that an API can be so many things (function, prop, parameter, property etc ...) and there are often different techniques used for each.

@mtias mtias added the [Feature] Extensibility The ability to extend blocks or the editing experience label Oct 18, 2024
@ralucaStan
Copy link
Contributor

ralucaStan commented Oct 18, 2024

Introducing stage in the lifecycle of a new/experimental API would be good for consumers to understand its maturity.
Between the 2 stages, private and stable it’s hard to know where an API is currently positioned ( as in closer to which stage).

This change could also encourage community feedback, which is ultimately the enabler for stabilizing something.
It could also enable the maintainers to track better progress on APIs by observing how long it's been in a certain stage. This could prompt faster decision-making.

When it comes to usage of experimental APIs, it's clear that the risks are different depending on where the API is in its lifecycle. Using an API that is closer to becoming stable should in theory be less risky. What is a risk? Breaking changes or full removal of API.

I think this information about API maturity could speak more to developers about the type of involvement they could have and the risks/limits that come with it.

I agree coming up with these stages could be tricky, and there might be downsides to this approach, but it would allow for more flexibility and predictability then now.

Good changes from the status quo are to:

  • understand when an API is closer to becoming public; open it for early adopters and guarantee it's not going to be removed. Breaking changes can still happen (or not)
  • highlight the stages where the probability of the API removal is high
  • highlight when people can start experimenting and submitting feedback (I'm thinking Bits)
  • give another parameter outside of the API age and dissolve any confusion around an API's current state
  • reflect the team's progress/opinion in terms of API maturity;

@peterwilsoncc
Copy link
Contributor

I feel this need everyday when developing on Gutenberg. For a long term and sustainable project, a way to have a feedback loop for APIs, just like we do for features is needed. I know it's something that WordPress never had (aside from a small period where we had the experimental APIs in Gutenberg). I don't have a solution, but it's definitely a problem that is worth solving if we want to be shipping the right stable APIs.

@youknowriad I haven't been able to think of anything for WordPress Core but for sites running the Gutenberg plugin, it might be worth making the opt-in string static so site's using the APIs can safely do so while testing with the plugin.

I opt in for sites running the Gutenberg plugin and acknowledge these APIs may change without notice and are unavailable in WordPress Core

Extenders making use of it would need to do a little error catching during the transition period but it's a solvable problem.

@ramonjd
Copy link
Member

ramonjd commented Oct 20, 2024

Warning: left field idea incoming...

Could the experimental page be built out as a place to document/track/solicit feedback for experimental APIs?

It might be as feature-rich as the plugins page, with update/alert badges. Automation would be ideal, e.g., a script could scrape the JS code base for relevant data, which the page could publish, and some other flag in experimental PHP code (under /lib/experiments) or something.

🤷🏻

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 22, 2024

is your request to allow opt-in to be easier only when running the Gutenberg plugin, or do you wish to be able to opt in for sites running WordPress Core?

@peterwilsoncc I didn't realize that we could distinguish between running and not running the Gutenberg plugin when I was writing down this issue. And yes, it could be a very good solution.

If a plugin wants to use unstable APIs, it's typically for an experimental feature that can be turned on/off in plugin settings. For example, WooCommerce has a Settings/Advanced/Features screen where you can enable a beta product editor:

Image

A new additional constraint would be that this beta editor can be active only when the Gutenberg plugin is installed and active. Today the purpose of the Gutenberg plugin is to provide a bi-weekly "technology preview" of what is coming to Core in the next release. And now it could also provide a "beta environment" to plugins that also want to offer their own "technology preview" features. It all fits together very well.

If you want to ship a feature to the general public, it won't be able to use the experimental APIs. The APIs need to be stabilized in Core before they can be used. That's certainly a limitation, but not a bad one.

How would the API for opting into the private APIs look like? Currently Core modules can do this:

__dangerousOptInToUnstableAPIsOnlyForCoreModules( 'I acknowledge...', '@wordpress/blocks' );

where there is a consent string that serves as a sort of password, and the module also needs to specify its name. Every module name can be used only once, and there is an allowlist of them. That partially prevents non-Core modules from impersonating as a Core module.

For experimental API access from plugins we could have a second function:

__dangerousOptInToUnstableAPIs( 'I confirm...' );

This function implementation would check globalThis.IS_GUTENBERG_PLUGIN. It would throw an error if the constant is false, and check the consent string otherwise.

How does that sound? @ralucaStan @gigitux @lysyjan would it satisfy the requirements for Woo and MailPoet plugins?

It would be also nice to have the private APIs documented and sorted into various stages. That could be a well-maintained private-apis.md file in the repo which would document each API and describe how stable it is.

@youknowriad
Copy link
Contributor

@jsnajdr Just noting that we actually already do that. We already use globalThis.IS_GUTENBERG_PLUGIN to avoid shipping some experimental APIs to Core. An example that comes to my mind is the wp.editor.registerEntityAction API.

So I had assumed the discussion was more general, about Core as well.

Also, I do think there's value in improving our communication around the different stages of the different APIs.

@gigitux
Copy link
Contributor

gigitux commented Oct 22, 2024

If a plugin wants to use unstable APIs, it's typically for an experimental feature that can be turned on/off in plugin settings. For example, WooCommerce has a Settings/Advanced/Features screen where you can enable a beta product editor:

This is not always true. For example, currently, we're using some private APIs for the Customize Your Store project that it is in production and belongs to the Woo onboarding flow. I shared some private APIs that we're using:

From a quick search, it looks like that we're using 20 times unlock():

Image

We can do this because these packages aren’t imported directly from WordPress Core; instead, we use the npm version.

My concern with allowing plugin to opt-in to the private APIs is that WordPress would end up in a similar situation again. I worry that we'd end up in a situation in which WordPress couldn't unlock an API for risk of breaking sites (@adamziel worked on the code, so this may not be an issue -- I suggested it & try to track string changes each release).

While I fully agree with our commitment to maintaining stable APIs, I’m not sure why we should take responsibility for not breaking plugins that use private APIs. By definition, these APIs are experimental, and it is the responsibility of plugin developers to ensure their plugins remain compatible with new versions of WordPress. As a platform, we should make this “contract” clear, and we should certainly communicate the status of private APIs more effectively and work towards stabilizing those that have been in use for several years and are relied upon by multiple plugins.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 22, 2024

we're using some private APIs for the Customize Your Store project that it is in production and belongs to the Woo onboarding flow.

If a production project needs private APIs, the first question is why these APIs are private and if it's still justified. There are many that could be stabilized right away.

Your first example uses the useGlobalSetting hook. This one was added 3 years ago in #35264 and it hasn't changed since. It has three parameters (path, block, source) and returns a state-like [ value, setValue] array. In Jan 2023 #47098 moved it from edit-site to block-editor, no other change ever happened. This hook and many other Global Styles APIs are a clear candidate for stabilization.

The second example uses the @wordpress/router package. What is special here is that:

  • the entire package is in fact private, it exports only private APIs
  • the Customize Your Store app could fully bundle it without losing anything, it doesn't need to be an externalized WP script
  • the app could also completely avoid using it: the @wordpress/router package is a thin wrapper around a public history package
  • the @wordpress/router package will probably soon disappear completely, after Gutenberg migrates Site Editor to one of the standard open source routers.

While I fully agree with our commitment to maintaining stable APIs, I’m not sure why we should take responsibility for not breaking plugins that use private APIs.

In my view the API stability policy is there also to protect the user from us engineers 🙂 If a plugin tries to use some API that's no longer there or is different, the user's site will break and the user will be harmed. Then various groups of engineers can argue with each other about whose fault it is and who is responsible, but that doesn't help the user much -- their site is broken.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 23, 2024

We already use globalThis.IS_GUTENBERG_PLUGIN to avoid shipping some experimental APIs to Core. An example that comes to my mind is the wp.editor.registerEntityAction API.

Yes, maybe we don't really need much more than that. In its current form, wp.editor.registerEntityAction is a public function that behaves as a noop outside the Gutenberg plugin. Technically, we made a commitment to keep the function there so that a wp.editor.registerEntityAction() call doesn't crash.

If I make an analogy with public/protected/private modifiers in PHP or C++, we want to introduce "protected" APIs that can be used more widely than "private" ones, namely by plugins. Then registerEntityAction would no longer be public, but it would be a protected API that a plugin explicitly needs to opt-in into.

So I had assumed the discussion was more general, about Core as well.

We can discuss also Core and access to private APIs without the Gutenberg plugin active. It's just that we don't have any nice solution for that yet, and also it's not clear who needs such a Core access and what the requirements are.

@youknowriad
Copy link
Contributor

We can discuss also Core and access to private APIs without the Gutenberg plugin active. It's just that we don't have any nice solution for that yet, and also it's not clear who needs such a Core access and what the requirements are.

For me the need is that we need a better feedback loop with extenders and Extenders can give us that feedback unless they ship their plugins to real users without the need for an additional Gutenberg plugin forced onto their users.

It's true that we've managed so far to have something "working" without it but it's far from ideal.

@joshuatf
Copy link
Contributor

It seems like we're discussing two things in this issue:

  1. How we can safely expose private APIs to third party plugins.
  2. How we can get feedback on those APIs so that they can eventually become stable.

On the 1st point, as I understand it Gutenberg could remove or introduce a breaking change to a private API at any point in time (please correct me if I'm wrong here). This makes it unsafe for plugins like WooCommerce to consume these APIs and subsequently difficult to accomplish point 2 for Gutenberg to get feedback on these APIs.

It might also be dangerous relying on the Gutenberg plugin being active as a means to expose private APIs. This seems like it's introducing a dependency that can't be fully controlled by the consuming plugin. For example, one plugin might require version X and the other requires version Y, with version X having the private API that plugin A needs, while breaking changes are made in version Y.

Using NPM would be a simple solution that would allow plugins to pin versions with the private APIs without introducing breaking changes in upcoming versions. However, this eliminates the benefits of exposing packages on the wp global and is costly in terms of performance if multiple plugins are all pinning different versions of packages in order to guarantee that a certain API is in place.

I wonder if there's an opportunity to create a minimal package that houses the private APIs and could be pinned by consumers. However, in practice I think there might be too many interdependencies for those private APIs, creating the same performance issues we were trying to avoid.

@adamziel
Copy link
Contributor

For me the need is that we need a better feedback loop with extenders and Extenders can give us that feedback unless they ship their plugins to real users without the need for an additional Gutenberg plugin forced onto their users.

I wanted to echo my comment from another thread. The consent string seems like an obvious thing to look at, but I think it's just a symptom of a deeper challenge with the dependency graph.

Here's my understanding of the situation:

  • @wordpress/dataviews are meant for Core once the API matures
  • Today it's shipped with the Gutenberg plugin and as an npm package
  • Part of maturing the API is getting feedback from the developers using dataviews in their projects
  • Expecting every dataviews-reliant plugin to depend on a specific version of Gutenberg is a big ask
  • Therefore, developers consume dataviews through npm, and with it they bring in all the npm dependencies, such as @wordpress/data
  • These plugins are built with @wordpress/scripts, which replaces the npm version of @wordpress/data with wp.data, but it cannot do the same for dataviews because there's no core version of dataviews

Does that sound right? If yes, that last steps effectively replaces the dependency graph used by @wordpress/dataviews. The assumption seems to be "I'm sure window.wp is compatible with all possible versions of this dependency". Is that assumption correct? Aside of the private APIs, that is.

One solution would be for all the dataviews-reliant plugins to require the Gutenberg plugin after all. Then, instead of bundling the package we could perform the substitution to window.wp.dataViews and keep a coherent dependency graph.

@mtias
Copy link
Member

mtias commented Oct 24, 2024

The idea of using the presence of the Gutenberg plugin to expose unstable APIs so plugins can get ahead, try, provide feedback, etc, is how we've been thinking about it already. Gutenberg has become, as a matter of fact, a channel for early testing of both user and developer features. Plugins that have a dependency on Gutenberg would be well aware of it and should help promote APIs that become solid to be part of Core releases.

I also agree with @youknowriad that it'd be good to formalize the state of some of these, whether that is through "stages" or something else.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 24, 2024

@adamziel The @wordpress/dataviews package is special because it's not a part of the WordPress platform. There is no wp-dataviews script you can enqueue, no wp.dataviews global that needs to have a reliable contract with consumer. It's just another NPM package you can use and bundle in your project. It has no WordPress compatibility constraints.

The only problem with @wordpress/dataviews is that it uses private APIs from @wordpress/components, which is a part of the platform. That creates its own set of issues as discussed in #63657.

This discussion is mainly about packages that i) are part of the platform, and ii) expose private experimental APIs that often change. If plugins could use these private APIs freely, an upgrade from WordPress 6.6 to 6.7 would likely break many sites.

@joshuatf
Copy link
Contributor

@youknowriad @mtias I have 2 concerns with requiring the Gutenberg plugin to be active to use these newer APIs:

  1. It requires another plugin install (Gutenberg) for some features that are currently shipped as enabled with WooCommerce core.

I think this point might signal that the underlying API should be marked as stable, but I'm not sure that a single plugin (Woo) can make that decision. Even once that decision is made, it means that we need to always install Gutenberg with at least the next 2 WP versions.

  1. If the private APIs can be removed or changed at any time, what happens when one plugin is using the APIs from Gutenberg version A while another plugin is using APIs from version B? Or what happens when plugins get auto-updated and the API is no longer available?

This seems like it might wreak havoc on consuming plugins relying on an API being present in a specific version of Gutenberg.

@adamziel
Copy link
Contributor

adamziel commented Oct 24, 2024

I think this:

The only problem with @wordpress/dataviews is that it uses private APIs from @wordpress/components, which is a part of the platform.

contradicts this:

It has no WordPress compatibility constraints.

@jsnajdr isn't that an implicit dependency on a specific WordPress / Gutenberg version?

@peterwilsoncc
Copy link
Contributor

The idea of using the presence of the Gutenberg plugin to expose unstable APIs so plugins can get ahead, try, provide feedback, etc, is how we've been thinking about it already. Gutenberg has become, as a matter of fact, a channel for early testing of both user and developer features. Plugins that have a dependency on Gutenberg would be well aware of it and should help promote APIs that become solid to be part of Core releases.

I couldn't agree more with this. Making it easier to opt-in when running Gutenberg (via a fixed string or some other means) would help make this feedback loop easier.

I have 2 concerns with requiring the Gutenberg plugin to be active to use these newer APIs:

1. It requires another plugin install (Gutenberg) for some features that are currently shipped as enabled with WooCommerce core.

I think this point might signal that the underlying API should be marked as stable, but I'm not sure that a single plugin (Woo) can make that decision. Even once that decision is made, it means that we need to always install Gutenberg with at least the next 2 WP versions.

2. If the private APIs can be removed or changed at any time, what happens when one plugin is using the APIs from Gutenberg version A while another plugin is using APIs from version B?  Or what happens when plugins get auto-updated and the API is no longer available?

This seems like it might wreak havoc on consuming plugins relying on an API being present in a specific version of Gutenberg.

The private APIs are, by definition, not ready for use in production. If a theme or plugin chooses to __dangerousOptInToUnstableAPIsOnlyForCoreModules() for production code, then it's up to the developer of the third-party code to manage the fact that that private API can be removed or changed at any time.

I do accept that sometime we (being WordPress Core developers) could sometime mark APIs stable more quickly but it needs to be done through an issue to allow for folks to discuss whether the API is, in fact, in good shape to be marked stable.

I DO want to make it easier for third party developers to assist with the stabalization of the APIs and making it easier for devs to use the APIs in non-production code will help open the feedback loop. What I don't want to do is end up back were we started of experimental APIs been used so widely in production code that WordPress Core gets stuck with the first or second draft of the API by making them available without running the Gutenberg plugin.

@joshuatf
Copy link
Contributor

The private APIs are, by definition, not ready for use in production. If a theme or plugin chooses to __dangerousOptInToUnstableAPIsOnlyForCoreModules() for production code, then it's up to the developer of the third-party code to manage the fact that that private API can be removed or changed at any time.

@peterwilsoncc I understand the conundrum and why we're moving this direction. However, if we're wanting to get early feedback on these APIs, I think this method might present some challenges to plugins.

As a 3PD, needing to install an additional plugin for a feature to work is less than ideal, but not a deal breaker. Needing to install a plugin for a feature to work AND not knowing if the (latest) installed version will even house the private APIs that allow said feature to work is a dealbreaker.

Unless I'm misunderstanding something, without a way to pin a specific version of the plugin (i.e., with the required APIs), there's not a way to guarantee that a feature is supported at any given time. I know that WooCommerce will not be able to reliably use or provide feedback on any of these APIs if that's the case.

@joshuatf
Copy link
Contributor

One more question I have if we opt for the plugin approach is how we safe guard against changes in a consumer plugin.

Do we need to have strict version checks to make sure the functions work as expected in the event a function is removed or signature changes?

if ( GUTENBERG_VERSION === '19.3.0' ) {
    doPrivateApiThing();
}

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 29, 2024

isn't that an implicit dependency on a specific WordPress / Gutenberg version?

@adamziel The contradiction can be resolved by distinguishing between:

  • the API @wordpress/dataviews exposes to its clients. This API is not exposed in WordPress in any way, it's the API of an NPM package, and is it governed only by semver rules. The WordPress backward compatibility policy doesn't apply here.
  • what APIs of the WordPress platform the @wordpress/dataviews package uses under the hood. If it's using a private component exported from @wordpress/components, that places a hard constraint on dataviews consumers: if the current policy proposal goes through, the Gutenberg plugin needs to be active for any dataviews-using plugin.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 29, 2024

@joshuatf I can offer two arguments that could address your concerns:

The private APIs wouldn't change or disappear abruptly and randomly. The changes can be discussed, prepared for, you should be able to detect whether you're dealing with the old or new version. The point is that for stable APIs, we guarantee that plugin authors don't need to do any extra work. Once a stable API is there, WordPress guarantees that it will continue to work exactly the same way, practically forever. But unstable API is not the exact opposite of that (random chaos) but rather that the plugin developer is expected to keep up with the latest changes and update their code.

Second, in the future we should have only very few permanently private APIs, if any. They should get stable in a reasonable amount of time. If a plugin succesfully implements a production-ready feature using a private API, that's a strong stabilizing force that creates pressure on Core to stabilize that API, so that plugins can ship a stable version of the feature to general public. That's one thing I hope that this proposal achieves: that the private status will always be only temporary, because we'll have a good process how to get new APIs widely used, get feedback, and improve them quickly.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 29, 2024

It would be also nice to have the private APIs documented and sorted into various stages. That could be a well-maintained private-apis.md file in the repo which would document each API and describe how stable it is.

I started working on the private API docs in #66558.

@psealock
Copy link
Contributor

psealock commented Oct 29, 2024

Requiring the presence of Gutenberg plugin to expose Private APIs is a good solution that doesn't work for the business demands of a plugin like WooCommerce whose features are often shipped under deadlines imposed either internally or by contractual obligations.

The ideal scenario would be to rely on semver via NPM, but as mentioned above by @joshuatf, this is costly on performance. What if there is a solution that offers the security of semver but forces developers to be respectful of changing APIs?

What if the opt-in string was per package and required pinning to a major version of that package?

__dangerousOptInToUnstableAPIsOnlyForCoreModules(
	'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
	'@wordpress/router',
	'1.10.0^',
	'woocommerce'
);
  • Gutenberg private packages can change APIs and bump major versions without fear of breaking plugins.
  • Plugin developers are aware that a new version of WP and its packages must be opted into. This forces awareness of potential changes and requires development effort to keep using APIs.
  • Using private APIs in production is now possible, but incurs an inherent cost that prohibits "set and forget" type development, encouraging devs to participate in stabilizing APIs.
  • Unmatched versions in the opt-in string could default to the requirement of Gutenberg plugin.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 31, 2024

doesn't work for the business demands of a plugin like WooCommerce whose features are often shipped under deadlines imposed either internally or by contractual obligations.

This seems to be a project management issue about priorities. If you have a contractual obligation to deliver something by a certain date, then obviously you want to implement that with stable APIs, and you cannot spend time trying out experimental APIs.

For stabilizing Core APIs, the overwhelming constraint is "quality": we need to be sure that the API is right and that we can commit to it long-term. Deadlines play a very small role in that process. If another project's major constraint is "time", that leads to problems. It's a classic project management "triangle" where a project is constrained by resources, time, scope and quality and you can improve one metric only by compromising on others.

What if the opt-in string was per package and required pinning to a major version of that package?

If I understand it correctly, this means that the opt-in call can fail. It can return an error saying either that the Gutenberg plugin is required and you don't have it, or that you're requesting a version of the package that is not available on this particular WordPress installation.

Then you'd have to implement some fallback using stable APIs. Having two versions of a feature, one stable and conservative, and the other is experimental and optional.

@psealock
Copy link
Contributor

psealock commented Nov 8, 2024

Thanks @jsnajdr

Deadlines play a very small role in that process.

Thats a good point. For the sake of the discussion best to avoid that perspective.

If I understand it correctly, this means that the opt-in call can fail.

Yes, thats exactly right. The failure is by design what would keep extension developers honest about using an unstable API. WooCommerce, for example, has an L-1 support policy so that only the current latest WP and the previous versions are supported. In this scenario lets say a Private API gets a major version bump, requiring all opted-in consumers to update their consent strings otherwise fatal errors will occur. WooCommerce is now greatly incentivised to proactively address the update, determine what code changes are required for the updated API, and finally handle the situation to avoid breakage. This would be a regular part of the development process and provide enough friction to dissuade lazy usage of private APIs.

We'd need to gracefully handle failed consent in the UI by showing a message to update WooCommerce. Unit testing with pre-release versions of Core WP can offer enough time to address issues. Also, new WooCommerce features based on unstable APIs are often hidden behind feature flags where merchants can opt in to the new experience. This allows for early feedback, which affects usage of unstable APIs and ultimately provides feedback to those APIs and enhancing stability.

@costasovo
Copy link
Contributor

@jsnajdr I'm very grateful for this discussion. Thank you for starting it! The idea of stages is great, and having information about how close to stabilization an API is would help us avoid touching fresh or temporarily unstable APIs and encourage us to experiment with the APIs that are close to becoming stable.

In MailPoet, we are working on the email editor, and we use a couple of private APIs. The editor is currently behind a feature flag, but we are working towards preparing it for a release. There is no hard deadline, but we would like to release it in the next couple of months. So, there is a motivation to help stabilize the APIs we use.

That's one thing I hope that this proposal achieves: that the private status will always be only temporary because we'll have a good process for how to get new APIs widely used, get feedback, and improve them quickly.

The stages will give us a clue about the stability of an API, but what about the process for stabilization? As I mentioned, we use private APIs, and we have the motivation to help stabilize them. What are the steps we should take to provide feedback and ask for stabilization? For example, we use ExperimantalBlockCanvas because we need one prop that is not available on the stable BlockCanvas. Shall we open an issue where we describe our use-case and ask for stabilization? Or do you imagine a different process for sharing feedback about the usage of an unstable API?

As for private APIs, they are available only when Gutenberg is active. I'm thinking about a situation when a private API becomes stable, and it may take a couple of months until it makes it to WP Core. I think it is a bit of a shame because if we know the API is stable and the private version in WP Core is the same, I think we could release the feature to users earlier.

@jsnajdr
Copy link
Member Author

jsnajdr commented Nov 13, 2024

we use ExperimantalBlockCanvas because we need one prop that is not available on the stable BlockCanvas. Shall we open an issue where we describe our use-case and ask for stabilization?

Yes, in this case open an issue or even a PR that adds the prop to BlockCanvas. I guess you need contentRef? That should be easy to stabilize, it provides a ref to the content element which anyone can get anyway using the .editor-styles-wrapper selector. There are also hooks like useFlashEditableBlocks that are private and could be either made public, or called inside BlockCanvas without having to expose them. This was also discussed in #57672, namely in #57672 (comment) where @youknowriad says that he would like to make contentRef and other props not needed at all. Let's discuss how to get there. The #57672 issue is about the Isolated Block Editor, I guess the MailPoet editor has a lot in common with that.

@ralucaStan
Copy link
Contributor

we use ExperimantalBlockCanvas because we need one prop that is not available on the stable BlockCanvas. Shall we open an issue where we describe our use-case and ask for stabilization?

Ideally we should discuss, maybe in a separate discussions, what is the best way for developers to submit proposals, how to advocate for API changes, what makes a valid proposal/feature request, etc.

While reading about React's release channels and how they expose experimental API, I came across their RFC system https://github.com/reactjs/rfcs.

React RFCs (Request for Comments) are a structured way for the React team and community to propose and discuss changes or additions to the React framework.

What I like about this process is that:

  • it offers one place to see upcoming changes, features, requests, separate for the library's codebase; great as a historical tracking of decisions
  • it has a collaborative mindset, it's the same mechanism used by internal/external developers
  • the community PRs need strong motivation, which must be written using a template. A template like this, in our case, could communicate what are the expectations around a solid request (things like WP use cases, etc)

@ciampo
Copy link
Contributor

ciampo commented Nov 25, 2024

Hey all 👋 I'm going to join the conversation and share / recap a few reflections that I had over the past days with @mirka, @jsnajdr, @tyxla and @jameskoster in the context of tidying up the @wordpress/components package in the context of the design systems work.

Currently, some APIs are named "experimental" but that can't be assumed as such, given that the code was released in past WordPress core versions. In parallel, the introduction of the private API mechanism allowed us to develop and iterate on new APIs — with all the consequences and nuances discussed above.

Therefore, as @jsnajdr also mentioned above, we are considering introducing the concept of stages. While there is consensus over the "initial" stage ("just started, don't use") and the "stable" stage (stable API, feel free to use), we'd like to gather more feedback on the remaining stages:

  • for the experimental stage, we could have either one or two stages. The two stages would be "early experimental" (use in Gutenberg only) and "advanced experimental" (can be used by third-party too) — the components would still be private in both stages and subject to API changes / breakages, with the main difference being where the feedback mainly comes from, and a hope that most of the large breaking changes would be addressed in the "early experimental" phase. The alternative would be to have a single "experimental" phase, where both Gutenberg and third-party developers are allowed to consume the private APIs and give feedback;
  • we also discussed the addition of a final "deprecated" stage

@oandregal
Copy link
Member

oandregal commented Nov 25, 2024

I come to this conversation trying to understand how to make DataViews avaliable to extenders, while not making it stable yet. Phases sound nice, so I tried to anchor this idea on specifics:

Phase Description How is it accessed? Where is it available?
Initial Not aimed to be used by extenders. Core/Gutenberg use them via private APIs, by bundling the package, etc. Available in WordPress & Gutenberg.
Candidate Extenders are encouraged to use it, but it can change. Core is actively working on making this stable but needs feedback. Via wp.gutenberg.dataviews? Only in Gutenberg?
Stable Can be used by anyone, backwards-compatible. wp.dataviews WordPress & Gutenberg

A key question for the "Candidate" phase is whether it requires Gutenberg or it also works with WordPress:

  1. If it depends on having Gutenberg active: we could have them under the wp.gutenberg namespace. This approach is more restrictive for extenders, as they can only use these APIs with Gutenberg. The main benefit is that it's clear that this APIs aren't ready for being backwards-compatible.
  2. If the "candidate API" can work in WordPress. We could have them under wp.candidate. This favors extenders, as they can rely on a minimum WordPress version to use these API and don't require Gutenberg at all. If we go this route, we'll need to understand better what it means to "change the API" and "when" can it be changed. In principle, I'd go with "the API can do any change in between WordPress major versions, but minor versions would remain "compatible" with each other. This is: DataViews could change the API in between 6.8 and 6.9, but it'd be the same for 6.8.1, 6.8.2, etc.

I think I'd go with the 2 scenario. Tying "candidate" to Gutenberg is more favorable for core but it can be too restrictive for extenders — reducing the value we'll get from introducing this phase (feedback from extenders).

@mirka
Copy link
Member

mirka commented Nov 25, 2024

for the experimental stage, we could have either one or two stages

I'd prefer not to split the experimental stage, unless it is purely a procedural difference.

  • Early experimental (alpha): We're still integrating it inside Gutenberg.
  • Advanced experimental (beta): We're satisfied with the level of integration inside Gutenberg, so we encourage early adopter extenders to test it.

This way, we can make a pretty straightforward decision on when it graduates from alpha to beta — no value judgments on "stability" per se. I wouldn't want extenders to assume that the beta stage will somehow have less breaking changes than the alpha. Upholding that expectation would be costly.

@tyxla
Copy link
Member

tyxla commented Nov 26, 2024

I understand being reluctant to have two experimental stages, but with a single one, we don't solve the problem for a primary consumer group: 3rd party plugin developers. And to me, that's the biggest and most important group we need to make this mechanism work for. Inside Gutenberg, we can still use both experimental APIs and private ones, so we're not really facing a problem. The real problem is the requirement of having Gutenberg installed - for plugins that can't guarantee that Gutenberg (the plugin) will be available.

So, with a single experimental stage, considering WP's BC expectations, third-party devs will still have to wait until an API is stable before using it, right? I think if that's the case, it defeats the purpose of having that mechanism in the first place.

To me, the most important reason in favor of a second experimental phase is to actually be able to make it part of WordPress, so it can be used by extenders. I expect this will be documented as a specific exception to the pre-existing BC guidelines, one where plugin developers intentionally use the experimental/private API in a careful way where they cover cases where APIs are missing or are different, and they're responsible for any potential breakages that occur based on any API changes.

In my view, the grand purpose behind this mechanism is to provide a vehicle for plugin developers to be early adopters of features and APIs (even if they're not stable just yet) and to contribute upstream with feedback and development, while taking a core-first approach in their product development strategies. Isn't that what the original issue aims to achieve, after all?

@nerrad
Copy link
Contributor

nerrad commented Nov 26, 2024

Don't we already have three phases?

Phase One: Locked in Gutenberg
Phase Two: Gutenberg only (not in WP release yet, but will be in next release)
Phase Three: Stable in WP

From my perspective, we simply need to formalize and "stand behind" the existing "phases" as the mechanism which 3rd party developers can rely on for feature availability.

WordPress should lean more towards feature plugins as the distribution mechanism for early access. It'd be useful to explore what options we could provide for more visibility of feature plugins and features they expose for third-party use. For example, what if there was a programmatic mechanism within WP for plugins to request the install of an official feature plugin (such as Gutenberg)? Then plugins like WooCommerce could use that mechanism to install Gutenberg if a user enables a flag opting-in to a new feature that is currently dependent on Gutenberg.

@oandregal
Copy link
Member

My main concern is not about the number of phases, but how do we anchor them to specifics: how extenders access experimental APIs that we can change?

I'm fine with as many phases as we want, as long as each one has its own purposes and differentiated mechanism. My suggestion was about anchoring the conversation in specifics to understand the lay of the land — the things we can and can't do. For example, how do we handle API changes (removing things, breaking backward-compatibility for experimental APIs, etc.)? can we even do that? Do we use globals (wp.candidate) or an utility (optInToExperimentalAPIThatWillChange)? etc.

@peterwilsoncc
Copy link
Contributor

Sorry folks, I needed to let this slip so I could prioritize my 6.7 release squad duties.

To me, the most important reason in favor of a second experimental phase is to actually be able to make it part of WordPress, so it can be used by extenders. I expect this will be documented as a specific exception to the pre-existing BC guidelines, one where plugin developers intentionally use the experimental/private API in a careful way where they cover cases where APIs are missing or are different, and they're responsible for any potential breakages that occur based on any API changes.

The big issue I have with that is that it would reintroduce the faux-stable APIs problem once they hit stage two are are available in WP Core.

When Core used the __ prefix to indicate experimental/unstable/private APIs they were largely ignored. When used by large plugins, Core became stuck with the API because breaking tens of millions of websites is a non-starter.

If WooCommerce or other large plugins wants to use unstable APIs in production code, that's fine but it's on WooCommerce developers to know the risk if they are working around the intentional difficulty to use them.

I am happy to use a static string in the Gutenberg plugin but, yes, the requirement would be for plugins using these APIs to do version comparisons, try...catch or a wrapper component to maintain compatibility.

For the GB plugin static string, I suggest something along the lines of: Opt-in to private apis while using gutenberg. I acknowledge these may change at any time and plugin authors are responsible for monitoring the API in WordPress/Gutenberg and maintaining all compatibility. I acknowledge plugins are recommended to use try...catch to account for this and that tickets reporting a backward compatibility break in a private API are most likely to be closed wont-fix.

My strong recommendations for plugins such as WooCommerce wishing to use these APIs is to only do so in experimental features (such as the Woo block editor) that users opt-in to. If a user is opting in to bleeding edge Woo, then I think it's fair to expect them to opt-in to bleeding edge WP too.

@jsnajdr
Copy link
Member Author

jsnajdr commented Dec 12, 2024

Thanks everyone for participating in this discussion! Let me propose a roadmap how we could move forward. There is very little new about the roadmap, it's been proposed before in #47785 and documented in the coding guidelines. It has just never been fully implemented and somewhat forgotten about.

Private APIs: Gutenberg packages expose private APIs using the lock/unlock mechanism. These private APIs are not supposed to be used by plugins or themes, they are used only to build the block editor and core blocks in WordPress and in the Gutenberg plugin. We don't plan to stabilize and make them public, as we want to keep the option to modify or remove them at any time, without any long-term commitment to others. We should make the lock/unlock mechanism tighter, maybe using some private Symbol instances as "keys", so that plugins can't access them by knowing the right consent string and trying various consent strings in a try/catch loop, as they can do now.

In #66558 I compiled a list of all 200+ private APIs, so that we can have a picture what the API surface looks like, and what is really used to build the real Gutenberg editor apps and core blocks.

Experimental APIs: What plugins really want are not private APIs, but experimental APIs. Every experimental API has a clear goal to become stable and public sooner or later, and we want plugins to test them out and participate in their development. And plugins want to use them in order to start developing future features today.

The experimental API would be exposed as regular package exports, without a lock/unlock mechanism, but these exports would be present only in the Gutenberg plugin. The version in Core (i.e., the scripts like wp-includes/js/dist/block-editor.js) would not have these exports at all. That can be implemented using the IS_GUTENBERG_PLUGIN constant.

There can be one "object" export, like:

import { experimentalApis } from '@wordpress/components';
// transpiled to `const { experimentalApis } = wp.components`
const { Menu } = experimentalApis;

Or we can resurrect the good old __experimental prefixes. The difference would be that the __experimental exports would be erased from the Core version of scripts.

import { __experimentalMenu as Menu } from '@wordpress/components';

Nothing about this is really new, @adamziel proposed this a long time ago in #47785 and @youknowriad recently mentioned in this discussion (#66197 (comment)) that we already use IS_GUTENBERG_PLUGIN to restrict some things to the plugin. But we currently do it very rarely, and we mostly guard experimental features and UIs rather than API endpoints. We'll need to use it much more and more consistently. Currently the default is to put experimental APIs into the "private" box.

What this means that a plugin can use the experimental APIs only when the Gutenberg plugin is installed. The APIs should be used only for experimental opt-in features, and the plugin should fail gracefully when Gutenberg plugin is not present. Exactly what @peterwilsoncc wrote earlier:

My strong recommendations for plugins such as WooCommerce wishing to use these APIs is to only do so in experimental features (such as the Woo block editor) that users opt-in to.

I know this is a very big limitation but I'm afraid we can't do any better. If something is in Core, it needs to follow the backward-compatibility policy and the experimental APIs don't satisfy that. The policy is here to protect WordPress users and their sites, and also to protect WordPress' reputation for supporting seamless automated no-brainer upgrades. If you have a site that uses a plugin, you don't really know if it internally attempts to use experimental APIs. That's a knowledge completely unaccessible to a regular user. But we want you to trust us that when you upgrade from WordPress 6.7 to 6.8, done on the day of the release, we did everything possible to not break your site.

Stages: Only the experimental APIs will have stages. Private APIs are not exposed externally, and most of them are stable and not supposed to be public. Assigning them to a stage would mean that they will progress through the stages and will eventually become public. But that's not true and it could create an unwanted incentive to be used by extenders ("it's going to be stable eventually anyway so I'm actually helping you test it when I use it!")

To really assign APIs to stages, we'll need to work "in the trenches" with individual API endpoints. Do we want to make it initially "private" and use it only in Gutenberg, and only later change to "experimental". Do we want to expose it to plugins from the beginning? Do we have enough use cases for it in Gutenberg itself, or do we need plugins to really validate it?

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 [Package] Private APIs /packages/private-apis [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

No branches or pull requests