-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Introduce block metadata files for dynamic blocks #14238
Conversation
* | ||
* @param string $file Path to the translation file to load. | ||
*/ | ||
function gutenberg_register_block_type_from_metadata( $file ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we see becoming a part of core implementation? I think for any new functions like this, I'm wondering if we should bother with the namespacing convention, since it was a pain point for deprecating Gutenberg-specific functions toward and after WordPress 5.0.
Wondering if instead it's enough to just if ( ! function_exists( 'register_block_type_from_metadata' ) ) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had it named this way initially. I wasn't sure what the process looks like so I opted for gutenberg_
prefixed version. I'm fine with updating it as suggested as soon as we figure out how this definition should look and work like.
* @param string $file Path to the translation file to load. | ||
*/ | ||
function gutenberg_register_block_type_from_metadata( $file ) { | ||
$metadata = json_decode( file_get_contents( $file ), true ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this implementation seems to be fine for a file which doesn't exist (file_get_contents
returns false
) or a file which is invalid JSON (json_decode
returns false
), only because empty
is very tolerant about what it receives. Could be a potential source of error in the future, at least if not tested against.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should use more checks with proper error messages. I will make those checks more granular 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also make sure to use wp_json_encode()
:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usages of file_get_contents()
in Core are typically prefix with an error control operator @file_get_conents()
and there are more resilient read functions in the WP_Filesystem class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure this part needs more work if we decide to introduce such API for reading JSON file. This PR is more exploratory. I expect it's going to be divided in more solid smaller PRs which will implement more granular features. If someone wants to work on this part, we can find ways to split this task :)
$render_callback_file = dirname( $file ) . '/' . $metadata['renderCallback']; | ||
if ( file_exists( $render_callback_file ) ) { | ||
require $render_callback_file; | ||
$settings['render_callback'] = 'gutenberg_render_block_' . str_replace( array( '/', '-' ), '_', $block_name ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it requires anonymous functions (PHP 5.4.0+), but I wonder if rather than relying on a specific naming convention for functions, we use the file returns semantics:
https://gist.github.com/aduth/ef27bfaae19a9959a263f1600b37c71d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't think too much about so far. I definitely would prefer to avoid using the specific conventions for naming functions. Another solution is to use a function which registers such callbacks as follows:
register_block_render_callback( 'core/block', 'render_block_core_block' );
which would be placed inside a PHP file which would also contain the definition. We would also have to change the way such file is required. Maybe we load metadata definitions in one go. Then at the time, we are about to process render_callbacks
for all registered blocks we require all files referenced as renderCallbacks
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The register_block_render_callback
could impose more requirements on the runtime which consumes from the file (i.e. the function needs to be defined somewhere), in case that might deter us.
A yet-other idea is treating it more like a template file, expecting it to output HTML directly. I'm not sure this is a good way to go though, and it's not obvious how the template would read from attributes (unless they were just some $attributes
global defined for use by the file).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of those existing render callbacks reference other custom functions so this might get tricky. I don't know, maybe this would work. I'd love to see some thoughts from developers better familiar with patterns used in other parts of WordPress core like @pento or @danielbachhuber.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure I follow. Why is $metadata['renderCallback']
a file and not simply a callable function?
Probably the best example of prior art to follow is register_shortcode()
(which accepts a callable function). I wouldn't be opposed to taking the template file approach instead, but core doesn't currently have the pattern of injecting variables into a template (see the long conversation in 21676)
ServerSideRender, | ||
} from '@wordpress/editor'; | ||
|
||
export default function ArchivesEdit( { attributes, setAttributes } ) { | ||
const { align, showPostCounts, displayAsDropdown } = attributes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we could extract separately? Can we not implement supports.align for server-registered blocks currently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I will tackle it and some other preparatory tasks in their own PRs. I'm using this PR to identify all work that is required to get us closer to have the ability to register blocks with metadata. However, I'm discovering that we have some issues with the existing blocks. className
and align
is often not defined on the server and therefore not exposed for render_callback
method.
"title": "Archives", | ||
"category": "widgets", | ||
"icon": { | ||
"src": "icon.js" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one kind of sticks out as far as being inconsistent with other file references like in edit
and renderCallback
. I see from #13693 this is done as such to support properties like foreground
and background
color assignments.
In all, I think it's okay, though I also wonder on a few variations:
- Optionally also allow
icon
to be assigned as a string if it only needs to assignsrc
- Create a pattern around "file reference with options", something like a tuple of
[ file, options ]
({ "icon": [ "icon.js", { "background": "red" } ] }
)
Was looking at some similar specifications, where src
is a pretty accepted convention for naming this type of resource (example).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optionally also allow
icon
to be assigned as a string if it only needs to assignsrc
It is reserved for the slug at the moment, which aligns with how you can register block's icon as of today. I would be happy to use the file name as a shortcut but then we need to figure out how to provide the slug differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is reserved for the slug at the moment, which aligns with how you can register block's icon as of today. I would be happy to use the file name as a shortcut but then we need to figure out how to provide the slug differently.
Oh right. That makes sense. I guess the other option is to inspect the shape of the string to see if it "appears" as a file path (e.g. has file extension, or starts with ./
)
} | ||
$registry = WP_Block_Type_Registry::get_instance(); | ||
if ( $registry->is_registered( $block_name ) ) { | ||
$registry->unregister( $block_name ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is part of it "overriding" ? Not something to be included with a final implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely, it was only added to make it work until it's fixed in master
:)
/** | ||
* WordPress dependencies | ||
*/ | ||
import { SVG, Path } from '@wordpress/components'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good example of: How do we communicate in the manifest specific dependencies of the block (#13693 (comment), assets.json
, etc).
We could have some NPM-style dependencies
property in block.json
, but we still have some challenges:
- How does WordPress resolve those?
- Or does it? (Do we ship something already bundled with dependencies resolved?)
I could imagine, for example, that the icon
file referenced is one which already has everything bundled. This becomes very wasteful for shared dependencies, so I think we'd need some system there, but it's not clear to me how that looks in a way which is agnostic to the WordPress script loader / globals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i18n and assets are two issues we need to resolve to move forward. They are both very complex :)
I'm starting to question whether we should be splitting JS code into several files: edit.js
, save.js
, icon.js
, transforms.js
, etc. It only makes it harder to manage their dependencies without bigger benefits. If you want to insert a given block into the editor, you will have to load all those assets anyway.
It might be also be not enough to define only renderCallback
, what if a plugin developer doesn't need a render callback but wants to expose REST API endpoint? How do they do it? What if plugin bundles multiple blocks into one JS file?
@@ -0,0 +1,18 @@ | |||
{ | |||
"name": "core/block", | |||
"title": "Reusable Block", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you thought about how translation will work? From #13693:
Localized properties are automatically wrapped in
__
function calls on the backend and the frontend of WordPress. These translations are added as an inline script to thewp-block-library
script handle.
A challenge here is in knowing the textdomain to use if we were to call __
automatically when registering the block. I could see this being as simple as an additional textdomain
property within the block manifest (which is maybe not great because it's not really inherent to a block). Or an argument when calling to register a block from a manifest file. Or some hacky wizardry with debug_backtrace
+ get_plugin_data
to extract the Text Domain
field from the plugin header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the biggest issue I identified so far. I still don't have a good idea of how this could work. textdomain
as another attribute might be an option. I think it should be rather explicitly defined than depend on some traversal techniques. If this code would be placed in PHP, all translated string would have to provide this argument anyway.
I was also wondering if we can dynamically wrap with i18n functions when loading metadata. E.g.:
$settings = array(
'title' => __( $metadata['title'], $metadata['textdomain'] ),
);
/cc @swissspidy
$render_callback_file = dirname( $file ) . '/' . $metadata['renderCallback']; | ||
if ( file_exists( $render_callback_file ) ) { | ||
require $render_callback_file; | ||
$settings['render_callback'] = 'gutenberg_render_block_' . str_replace( array( '/', '-' ), '_', $block_name ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't think too much about so far. I definitely would prefer to avoid using the specific conventions for naming functions. Another solution is to use a function which registers such callbacks as follows:
register_block_render_callback( 'core/block', 'render_block_core_block' );
which would be placed inside a PHP file which would also contain the definition. We would also have to change the way such file is required. Maybe we load metadata definitions in one go. Then at the time, we are about to process render_callbacks
for all registered blocks we require all files referenced as renderCallbacks
?
* @param string $file Path to the translation file to load. | ||
*/ | ||
function gutenberg_register_block_type_from_metadata( $file ) { | ||
$metadata = json_decode( file_get_contents( $file ), true ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should use more checks with proper error messages. I will make those checks more granular 👍
* | ||
* @param string $file Path to the translation file to load. | ||
*/ | ||
function gutenberg_register_block_type_from_metadata( $file ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had it named this way initially. I wasn't sure what the process looks like so I opted for gutenberg_
prefixed version. I'm fine with updating it as suggested as soon as we figure out how this definition should look and work like.
} | ||
$registry = WP_Block_Type_Registry::get_instance(); | ||
if ( $registry->is_registered( $block_name ) ) { | ||
$registry->unregister( $block_name ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely, it was only added to make it work until it's fixed in master
:)
"title": "Archives", | ||
"category": "widgets", | ||
"icon": { | ||
"src": "icon.js" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optionally also allow
icon
to be assigned as a string if it only needs to assignsrc
It is reserved for the slug at the moment, which aligns with how you can register block's icon as of today. I would be happy to use the file name as a shortcut but then we need to figure out how to provide the slug differently.
ServerSideRender, | ||
} from '@wordpress/editor'; | ||
|
||
export default function ArchivesEdit( { attributes, setAttributes } ) { | ||
const { align, showPostCounts, displayAsDropdown } = attributes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I will tackle it and some other preparatory tasks in their own PRs. I'm using this PR to identify all work that is required to get us closer to have the ability to register blocks with metadata. However, I'm discovering that we have some issues with the existing blocks. className
and align
is often not defined on the server and therefore not exposed for render_callback
method.
@@ -0,0 +1,18 @@ | |||
{ | |||
"name": "core/block", | |||
"title": "Reusable Block", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the biggest issue I identified so far. I still don't have a good idea of how this could work. textdomain
as another attribute might be an option. I think it should be rather explicitly defined than depend on some traversal techniques. If this code would be placed in PHP, all translated string would have to provide this argument anyway.
I was also wondering if we can dynamically wrap with i18n functions when loading metadata. E.g.:
$settings = array(
'title' => __( $metadata['title'], $metadata['textdomain'] ),
);
/cc @swissspidy
/** | ||
* WordPress dependencies | ||
*/ | ||
import { SVG, Path } from '@wordpress/components'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i18n and assets are two issues we need to resolve to move forward. They are both very complex :)
I'm starting to question whether we should be splitting JS code into several files: edit.js
, save.js
, icon.js
, transforms.js
, etc. It only makes it harder to manage their dependencies without bigger benefits. If you want to insert a given block into the editor, you will have to load all those assets anyway.
It might be also be not enough to define only renderCallback
, what if a plugin developer doesn't need a render callback but wants to expose REST API endpoint? How do they do it? What if plugin bundles multiple blocks into one JS file?
"attributes": { | ||
"label": { | ||
"type": "string", | ||
"default": "Search" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
label
and buttonText
are tricky, too. Their default values were originally wrapped with i18n function. How do we apply it through JSON file?
At this moment, it'd be probably better to close this one and use it as an example later when all static blocks are fully converted to use |
Description
This PR in its initial form tries to explore how the current proposal shared in the block registration RFC (#13693) could look with all dynamics blocks registered by Gutenberg core.
Dynamic blocks covered:
All other blocks should be covered separately.
TODO
renderCallback
implementation.icon
(slug), other fields from RFC not used so far)