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

Plugin: Try API for making experiments limited to WordPress codebase #41278

Closed
wants to merge 1 commit into from

Conversation

gziolo
Copy link
Member

@gziolo gziolo commented May 24, 2022

What?

Based on the discussion from #40316.

Builds on the idea shared by @noisysocks in #40316 (comment):

One idea maybe worth investigating: Could we make it so that experimental functions and components can only be accessed via an import ... from '@wordpress/...'; statement? That is, they won't appear in the wp global. This is already how WordPress code is written so it shouldn't affect Core features. Adventurous third parties can use the experimental APIs but only by adding the @wordpress package to their package.json and therefore vendoring the code. When the experimental code is eventually deleted from Core it will continue to exist in the plugin's node_modules until the plugin is updated.

I went for a simpler solution, for now, to explore how we can build a formal API that makes it possible to use all experimental APIs inside the Gutenberg plugin and WordPress core without exposing them to plugins and themes.

Why?

To make experimental APIs limited to usage only in WordPress codebase.

How?

I use one of the existing packages that are used internally - @wordpress/interface. It's a less important part at the moment where to put the code. What matters is that the secretKey is not available to everyone through wp global. The value assigned to the secretKey gets generated on every build. In practice, it means it will change with every new public release for WordPress so developers won't be able to hardcode this version. We can think about an even more elaborate approach if a stable key for a single WP release version is still a concern.

Screenshot 2022-05-24 at 11 58 18

For the Gutenberg plugin we can use a predictable secrectKey, for example: GUTENBERG to allow plugins to use experimental APIs only with the Gutenberg plugin installed.

Defining experiments in the package

With that, we have a formal API to define an experiment (example for @wordpress/block-editor package):

const __experiments = createExperiments( {
 	test( name ) {
 		// eslint-disable-next-line no-console
 		console.log( `Executed with "${ name }".` );
 	},
 } );

 export { __experiments };

which currently would be code as:

export function __experimentalTest( name ) {
 	// eslint-disable-next-line no-console
	console.log( `Executed with "${ name }".` );
}

Consuming experiments

To use the experimental method we need to access the exposed __experiments helper function and combine it with a secret key that is available only internally in the bundled code:

import { __experiments } from '@wordpress/block-editor';
import { secretKey } from '@wordpress/interface';

const test = __experiments( 'test', secretKey );

test( 'hello world' );

which is equivalent to the usage today:

import { __experimenalTest as test } from '@wordpress/block-editor';

test( 'hello world' );

@gziolo gziolo added [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible Gutenberg Plugin Issues or PRs related to Gutenberg Plugin management related efforts labels May 24, 2022
@gziolo gziolo requested a review from adamziel May 24, 2022 09:21
@gziolo gziolo self-assigned this May 24, 2022
@gziolo gziolo changed the title Plugin: Try API for experiments only in the plugin Plugin: Try API for making experiments limited to WordPress codebase May 24, 2022
@github-actions
Copy link

Size Change: +563 B (0%)

Total Size: 1.24 MB

Filename Size Change
build/block-editor/index.min.js 151 kB +512 B (0%)
build/edit-post/index.min.js 30.4 kB +51 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.51 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/style-rtl.css 14.5 kB
build/block-editor/style.css 14.5 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 59 B
build/block-library/blocks/avatar/style.css 59 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 95 B
build/block-library/blocks/comments/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 961 B
build/block-library/blocks/gallery/editor.css 964 B
build/block-library/blocks/gallery/style-rtl.css 1.51 kB
build/block-library/blocks/gallery/style.css 1.51 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 333 B
build/block-library/blocks/group/editor.css 333 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 738 B
build/block-library/blocks/image/editor.css 737 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.95 kB
build/block-library/blocks/navigation/style.css 1.94 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 395 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 260 B
build/block-library/blocks/paragraph/style.css 260 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 69 B
build/block-library/blocks/post-comments-form/editor.css 69 B
build/block-library/blocks/post-comments-form/style-rtl.css 495 B
build/block-library/blocks/post-comments-form/style.css 495 B
build/block-library/blocks/post-comments/editor-rtl.css 77 B
build/block-library/blocks/post-comments/editor.css 77 B
build/block-library/blocks/post-comments/style-rtl.css 628 B
build/block-library/blocks/post-comments/style.css 628 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 370 B
build/block-library/blocks/pullquote/style.css 370 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 369 B
build/block-library/blocks/query/editor.css 369 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 759 B
build/block-library/blocks/site-logo/editor.css 759 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 504 B
build/block-library/blocks/table/editor.css 504 B
build/block-library/blocks/table/style-rtl.css 625 B
build/block-library/blocks/table/style.css 625 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 149 B
build/block-library/blocks/template-part/editor.css 149 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 561 B
build/block-library/blocks/video/editor.css 563 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 993 B
build/block-library/common.css 990 B
build/block-library/editor-rtl.css 10.2 kB
build/block-library/editor.css 10.3 kB
build/block-library/index.min.js 181 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 11.6 kB
build/block-library/style.css 11.6 kB
build/block-library/theme-rtl.css 689 B
build/block-library/theme.css 694 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47.1 kB
build/components/index.min.js 227 kB
build/components/style-rtl.css 14.5 kB
build/components/style.css 14.5 kB
build/compose/index.min.js 11.7 kB
build/core-data/index.min.js 14.6 kB
build/customize-widgets/index.min.js 11.2 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 7.98 kB
build/date/index.min.js 32 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.69 kB
build/edit-navigation/index.min.js 16 kB
build/edit-navigation/style-rtl.css 4.05 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/style-rtl.css 7.08 kB
build/edit-post/style.css 7.08 kB
build/edit-site/index.min.js 47.9 kB
build/edit-site/style-rtl.css 7.73 kB
build/edit-site/style.css 7.71 kB
build/edit-widgets/index.min.js 16.4 kB
build/edit-widgets/style-rtl.css 4.4 kB
build/edit-widgets/style.css 4.4 kB
build/editor/index.min.js 38.5 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.7 kB
build/element/index.min.js 4.3 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.9 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.1 kB
build/nux/style-rtl.css 744 B
build/nux/style.css 741 B
build/plugins/index.min.js 1.98 kB
build/preferences-persistence/index.min.js 2.23 kB
build/preferences/index.min.js 1.32 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 628 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.2 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.2 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@adamziel
Copy link
Contributor

@gziolo I like that it enables the usage of the __experimental features in the plugin without exposing them in the public API. The more I look at it, though, the more I don't like the extra overhead.

I wonder whether a webpack plugin could add these transforms in a way that keeps the existing code working?

@luisherranz
Copy link
Member

I wonder whether a webpack plugin could add these transforms in a way that keeps the existing code working?

Yes, there are Webpack plugins to do that, like this or this. Not sure if that approach will work in all instances, though. Maybe you can try using a regexp like /const __experimental[\w_]* =/i and /function __experimental[\w_]*\(/i and fine-tune them (search for false positives and so on).

Or just change the __experimental's with __SECRET_EXPERIMENTAL_API_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 🙂🤷‍♂️

@mcsf
Copy link
Contributor

mcsf commented May 24, 2022

Or just change the __experimental's with __SECRET_EXPERIMENTAL_API_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 🙂🤷‍♂️

This is exactly the idea that Grzegorz was entertaining in conversation with me earlier today. :) Or, more realistically, something like __secretInternals.

@luisherranz
Copy link
Member

__doNotUseInYourPlugin? It can't be more explicit than that 😄

@@ -1,2 +1,13 @@
export * from './components';
export { store } from './store';

export const secretKey = process.env.WP_SECRET_KEY;
Copy link
Contributor

Choose a reason for hiding this comment

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

It's possible, probable even, I am totally missing something but wouldn't this lead to the problem of plugins being able to import the secret key?

I ask this:

  • knowing this is a really early POC so maybe I should wait
  • acknowledging Adam's earlier comment about the overhead.

It's not that I don't trust plugins not to import the secret key if it's available, but it kind of is.

Copy link
Contributor

@draganescu draganescu May 25, 2022

Choose a reason for hiding this comment

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

@peterwilsoncc the way I understand this is that it solves the problem of experiments not being exposed in wp.* JavaScript globals. Plus the key even if you get it, it changes with every release, therefore you'll never be able to rely on it for more than a few months. This is a great leap compared to how __experimental* is within a simple wp.* call AND sometimes it remains for years unchanged, therefore "reliable".

Copy link
Member Author

Choose a reason for hiding this comment

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

It looks like @draganescu answered the question. This is the related part I included in the description:

What matters is that the secretKey is not available to everyone through wp global. The value assigned to the secretKey gets generated on every build. In practice, it means it will change with every new public release for WordPress so developers won't be able to hardcode this version. We can think about an even more elaborate approach if a stable key for a single WP release version is still a concern.

The trick is that secretKey is bundled with webpack into every entry point that consumes the experimental/internal/private APIs. So in practice, secretKey would never be available from WordPress core (and Gutenberg) in the browser.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks both. I was confused between internal exports for the package to use, vs external exports to the wp global.

I think I have it now, thanks for your patience explaining.

@gziolo
Copy link
Member Author

gziolo commented May 26, 2022

__doNotUseInYourPlugin? It can't be more explicit than that 😄

This or __internalSecretDoNotUseYet is a much better name than __experimental or __unstable. The naming we use at the moment turned out to be confusing and open to too many interpretations. In many places, we use __experimental to expose APIs from one entry point to another to avoid code duplication. A great example is experimental components from @wordpress/components. In some places, we could find a way to duplicate the code as a temporary measure, but it wouldn't be good for users because of the increased size of the bundles they download.

@peterwilsoncc
Copy link
Contributor

I've turned this over in my head while on holidays and I don't think it's suitable after all.

Changing the secret key each time the bundles are build will result in two things:

  1. The environment variable will need to be included in the version hash
    • this will modify the generated files each time the build is run and result in git/svn becoming dirty as a lot of these files are committed to WP-Develop
    • The npm tests on WP-Develop will never pass due to these lines.
  2. The bundle for each WordPress update will need to include all the JavaScript files
    • this caused problems for a minor release some time back (I'll research the exact version when I return from leave)
    • bigger bundles increase the chance an update will fail

I kind of wish this hadn't occurred to me but it's probably a good thing in the long run.

@adamziel
Copy link
Contributor

adamziel commented Jun 10, 2022

Changing the secret key each time the bundles are build will result in two things

@peterwilsoncc It could be a version hash or something else equally stable across all builds of a given release.

The bundle for each WordPress update will need to include all the JavaScript files

Stable for just the major versions, then?

@peterwilsoncc
Copy link
Contributor

It could be a version hash or something else equally stable across all builds of a given release.

I wonder if this would lead to the same problem: plugin authors would hard code a range of keys for various releases.

I do appreciate you rubber ducking this, though. Thanks.

I'll research the exact version when I return from leave

I've followed up my comment from above, the exact version was WP 5.7.1.

@adamziel
Copy link
Contributor

I wonder if this would lead to the same problem: plugin authors would hard code a range of keys for various releases.

Perhaps! At the same time, even with a system where a new key is generated per build, plugin authors could create an automation that keeps track of all the newly published keys. Ultimately, it boils down to the question: where do we set the line? To me, having a per-release key doesn't sound too bad as long as everything around it screams "do not use this key, this is intentionally hard," I'm thinking about:

  • The key variable name itself (e.g. INTERNAL_SECRET_KEY_DO_NOT_USE_IN_PLUGINS)
  • The doc block around it
  • The name of the "get experiment" function (e.g. __internalGetExperimentDoNotUseInPlugins)

...and so on

@gziolo
Copy link
Member Author

gziolo commented Jun 14, 2022

Thank you for all the comments. I just wanted to emphasize that I intended to gather feedback on whether we should further explore the idea of creating a programmatic way to prevent accidental usage of internal Gutenberg APIs in WordPress core. Once we are all on the same page, we can start thinking about more creative ways to make that happen that are fully compatible with WordPress core requirements. Currently, it doesn't seem like the idea has enough support in the ongoing discussion in #40316.

@peterwilsoncc
Copy link
Contributor

I just wanted to emphasize that I intended to gather feedback on whether we should further explore the idea of creating a programmatic way to prevent accidental usage of internal Gutenberg APIs in WordPress core.

Absolutely understood. This is a try PR in draft to help inform the ticket rather than a merge proposal :)

@gziolo
Copy link
Member Author

gziolo commented Jul 7, 2022

This PR was opened only to show the concept. I don't plan to continue working on it in the close future.

@gziolo gziolo closed this Jul 7, 2022
@gziolo gziolo deleted the try/experimental-apis branch July 7, 2022 11:20
@adamziel
Copy link
Contributor

adamziel commented Aug 17, 2022

Here's an alternative idea – each package would register itself as follows:

const ACCESS_TOKEN = {}; // An object with unique identity
registerPackage( '@wordpress/data', module.exports, ACCESS_TOKEN );

registerPackage would only accept the known @wordpress packages and remember the ACCESS_TOKEN for later

Different packages would use their own ACCESS_TOKEN to import the experimental APIs:

getPackage( '@wordpress/data', ACCESS_TOKEN ).__experimentalAPI

getPackage would only return the requested module if the ACCESS_TOKEN was used to register another @wordpress package.

Here's a proof of concept:

const CORE_PACKAGES_NAMES = [ '@wordpress/data', '@wordpress/core-data', '@wordpress/element', '@wordpress/edit-post', '@wordpress/edit-widgets' ];
const accessTokens = [];
const privatePackages = {};

export function registerPackage( packageName, package, accessToken ) {
	if ( ! CORE_PACKAGES_NAMES.includes( packageName ) ) {
		throw new Error(
			`Cannot register a non-WordPress package ${ packageName }.`
		);
	}
	if ( packageName in privatePackages ) {
		throw new Error( `Package ${ packageName } is already registered.` );
	}
	accessTokens.push( accessToken );
	privatePackages[ packageName ] = package;
}

export function getPackage( packageName, accessToken ) {
	if ( ! ( packageName in privatePackages ) ) {
		throw new Error( `Package ${ packageName } is not registered yet.` );
	}
	if ( ! accessTokens.includes( accessToken ) ) {
		return null;
	}
	return privatePackages[ packageName ];
}

window.wp would remain the recommended way of using the public APIs. A plugin that would like to work around all these limitations and use the experimental APIs anyway would have to:

  1. Realize a private importing system exists
  2. Read the code where the risks would be spelled out in capital letters
  3. Register its own access token by passing e.g. @wordpress/edit-post as packageName – raising an error as soon as the real package is loaded

registerPackage could also require ACCESS_TOKEN to have a certain shape, inspired by React's __SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:

const ACCESS_TOKEN = {
    i_understand_this_gives_me_access_to_internal_code_that_may_be_removed_at_any_time: true,
    i_want_my_plugin_to_break_on_the_next_wordpress_release: true
}

To me, that's enough to claim these private functions are internal and not a subject to BC guarantees. Thoughts @gziolo @peterwilsoncc @mcsf @talldan @noisysocks @azaozz ?

@adamziel
Copy link
Contributor

adamziel commented Aug 17, 2022

Actually, perhaps even the following deterrent would suffice?

// in @wordpress/data
export const getInternalFunctions( consent ) {
   // ...
}

// In another package
import { __dangerousGetInternalAPIs } from '@wordpress/data';
const { __experimentalSomething } = __dangerousGetInternalAPIs({
    i_want_my_plugin_to_break_on_the_next_wordpress_release: true,
    current_wordpress_release: '6.0' // is this too much?
});

@gziolo
Copy link
Member Author

gziolo commented Aug 17, 2022

@adamziel, this is a very promising idea. Let me summarize how I understand your proposal:

  1. Every core package from wp namespace that uses or exposes experimental API needs to register its own access token. The token could be as simple as const accessToken = uuid.v5();.
  2. There is a closed list of all packages using wp namespace so the token gets registered only once per package. This way plugins can't override the list and inject their token.
  3. All registered access tokens grant access to experimental APIs. Every package can import its locally available access token. It never leaks to the public API.

Very clever. Instead of having a single global access token that needs to be obscured, we would use truly internal access tokens generated on the fly for every core package.

@peterwilsoncc
Copy link
Contributor

I think this is really promising: the more difficult it is for an extender to access the API, the clearer the warning of upcoming change.

@adamziel
Copy link
Contributor

I turned that idea into a PR – let's discuss there: #43386

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Gutenberg Plugin Issues or PRs related to Gutenberg Plugin management related efforts [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants