Skip to content

Commit f5efc30

Browse files
gzioloaduth
andauthored
Block API: Extend register_block_type_from_metadata to handle assets (#22519)
* Blocks: Extend `register_block_type_from_metadata` to hanle assets The proposed approach follows the solution proposed in Block Registration RFC. * Update lib/compat.php Co-authored-by: Andrew Duthie <andrew@andrewduthie.com> * Map explicitly from metadata to settings to filter out unwanted fields * Improve error handling by following register from the WP_Block_Type_Registry * Update unit tests for register_block_from_metadata * Add handling for scripts/style handles and paths * Remove styleVariations alias in favor of the supported styles property * Improve readability of the metada file path handling * Introduce helper functions for working with asset files * Docs: Reflect proposed changes for block.json handling of asset files' * Correct the way asset paths are handled * Add missing example field in block metadata processing * Add basic unit tests to cover functions that register handles * Add more tests covering automatic block asset registration Co-authored-by: Andrew Duthie <andrew@andrewduthie.com>
1 parent 8dca7e9 commit f5efc30

8 files changed

Lines changed: 423 additions & 33 deletions

docs/rfc/block-registration.md

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This RFC is intended to serve both as a specification and as documentation for t
66

77
Behind any block type registration is some abstract concept of a unit of content. This content type can be described without consideration of any particular technology. In much the same way, we should be able to describe the core constructs of a block type in a way which can be interpreted in any runtime.
88

9-
In more practical terms, an implementation should fulfill requirements that...
9+
In more practical terms, an implementation should fulfill requirements that:
1010

1111
* A block type registration should be declarative and context-agnostic. Any runtime (PHP, JS, or other) should be able to interpret the basics of a block type (see "Block API" in the sections below) and should be able to fetch or retrieve the definitions of the context-specific implementation details. The following things should be made possible:
1212
* Fetching the available block types through REST APIs.
@@ -16,7 +16,7 @@ In more practical terms, an implementation should fulfill requirements that...
1616

1717
It can statically analyze the files of any plugin to retrieve blocks and their properties.
1818
* It should not require a build tool compilation step (e.g. Babel, Webpack) to author code which would be referenced in a block type definition.
19-
* There should allow the potential to dynamically load ("lazy-load") block types, or parts of block type definitions. It practical terms, it means that the editor should be able to be loaded without enqueuing all the assets (scripts and styles) of all block types. What it needs is the basic metadata (`title`, `description`, `category`, `icon`, etc...) to start with. It should be fine to defer loading all other code (`edit`, `save`, `transforms`, and other JavaScript implementations) until it is explicitly used (inserted into the post content).
19+
* There should allow the potential to dynamically load ("lazy-load") block types, or parts of block type definitions. It practical terms, it means that the editor should be able to be loaded without enqueuing all the assets (scripts and styles) of all block types. What it needs is the basic metadata (`title`, `description`, `category`, `icon`, etc) to start with. It should be fine to defer loading all other code (`edit`, `save`, `transforms`, and other JavaScript implementations) until it is explicitly used (inserted into the post content).
2020

2121
## References
2222

@@ -67,7 +67,7 @@ To register a new block type, start by creating a `block.json` file. This file:
6767
"category": "text",
6868
"parent": [ "core/group" ],
6969
"icon": "star",
70-
"description": "Shows warning, error or success notices ...",
70+
"description": "Shows warning, error or success notices",
7171
"keywords": [ "alert", "message" ],
7272
"textdomain": "my-plugin",
7373
"attributes": {
@@ -90,10 +90,10 @@ To register a new block type, start by creating a `block.json` file. This file:
9090
"message": "This is a notice!"
9191
},
9292
},
93-
"editorScript": "build/editor.js",
94-
"script": "build/main.js",
95-
"editorStyle": "build/editor.css",
96-
"style": "build/style.css"
93+
"editorScript": "file:./build/index.js",
94+
"script": "file:./build/script.js",
95+
"editorStyle": "file:./build/index.css",
96+
"style": "file:./build/style.css"
9797
}
9898
```
9999

@@ -295,7 +295,7 @@ Plugins and Themes can also register [custom block style](/docs/designers-develo
295295
"example": {
296296
"attributes": {
297297
"message": "This is a notice!"
298-
},
298+
}
299299
}
300300
}
301301
```
@@ -312,7 +312,7 @@ Plugins and Themes can also register [custom block style](/docs/designers-develo
312312
* Property: `editorScript`
313313

314314
```json
315-
{ "editorScript": "build/editor.js" }
315+
{ "editorScript": "file:./build/index.js" }
316316
```
317317

318318
Block type editor script definition. It will only be enqueued in the context of the editor.
@@ -325,7 +325,7 @@ Block type editor script definition. It will only be enqueued in the context of
325325
* Property: `script`
326326

327327
```json
328-
{ "script": "build/main.js" }
328+
{ "script": "file:./build/script.js" }
329329
```
330330

331331
Block type frontend script definition. It will be enqueued both in the editor and when viewing the content on the front of the site.
@@ -338,7 +338,7 @@ Block type frontend script definition. It will be enqueued both in the editor an
338338
* Property: `editorStyle`
339339

340340
```json
341-
{ "editorStyle": "build/editor.css" }
341+
{ "editorStyle": "file:./build/index.css" }
342342
```
343343

344344
Block type editor style definition. It will only be enqueued in the context of the editor.
@@ -351,7 +351,7 @@ Block type editor style definition. It will only be enqueued in the context of t
351351
* Property: `style`
352352

353353
```json
354-
{ "style": "build/style.css" }
354+
{ "style": "file:./build/style.css" }
355355
```
356356

357357
Block type frontend style definition. It will be enqueued both in the editor and when viewing the content on the front of the site.
@@ -387,18 +387,23 @@ In the case of [dynamic blocks](/docs/designers-developers/developers/tutorials/
387387

388388
### `WPDefinedAsset`
389389

390-
The `WPDefinedAsset` type is a subtype of string, where the value must represent an absolute or relative path to a JavaScript or CSS file.
390+
The `WPDefinedAsset` type is a subtype of string, where the value represents a path to a JavaScript or CSS file relative to where `block.json` file is located. The path provided must be prefixed with `file:`. This approach is based on how npm handles [local paths](https://docs.npmjs.com/files/package.json#local-paths) for packages.
391+
392+
An alternative would be a script or style handle name referencing a registered asset using WordPress helpers.
391393

392394
**Example:**
393395

394396
In `block.json`:
395397
```json
396-
{ "editorScript": "build/editor.js" }
398+
{
399+
"editorScript": "file:./build/index.js",
400+
"editorStyle": "my-editor-style-handle"
401+
}
397402
```
398403

399404
#### WordPress context
400405

401-
In the context of WordPress, when a block is registered with PHP, it will automatically register all scripts and styles that are found in the `block.json` file.
406+
In the context of WordPress, when a block is registered with PHP, it will automatically register all scripts and styles that are found in the `block.json` file and use file paths rather than asset handles.
402407

403408
That's why, the `WPDefinedAsset` type has to offer a way to mirror also the shape of params necessary to register scripts and styles using [`wp_register_script`](https://developer.wordpress.org/reference/functions/wp_register_script/) and [`wp_register_style`](https://developer.wordpress.org/reference/functions/wp_register_style/), and then assign these as handles associated with your block using the `script`, `style`, `editor_script`, and `editor_style` block type registration settings.
404409

@@ -419,7 +424,7 @@ build/
419424

420425
In `block.json`:
421426
```json
422-
{ "editorScript": "build/index.js" }
427+
{ "editorScript": "file:./build/index.js" }
423428
```
424429

425430
In `build/index.asset.php`:
@@ -480,7 +485,7 @@ Implementation should follow the existing [get_plugin_data](https://codex.wordpr
480485
There is also a new API method proposed `register_block_type_from_metadata` that aims to simplify the block type registration on the server from metadata stored in the `block.json` file. This function is going to handle also all necessary work to make internationalization work seamlessly for metadata defined.
481486

482487
This function takes two params:
483-
- `$path` (`string`) – path to the folder where the `block.json` file is located.
488+
- `$path` (`string`) – path to the folder where the `block.json` file is located or full path to the metadata file if named differently.
484489
- `$args` (`array`) – an optional array of block type arguments. Default value: `[]`. Any arguments may be defined. However, the one described below is supported by default:
485490
- `$render_callback` (`callable`) – callback used to render blocks of this block type.
486491

lib/compat.php

Lines changed: 182 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,132 @@
99
*/
1010

1111
if ( ! function_exists( 'register_block_type_from_metadata' ) ) {
12+
/**
13+
* Removes the block asset's path prefix if provided.
14+
*
15+
* @since 5.5.0
16+
*
17+
* @param string $asset_handle_or_path Asset handle or prefixed path.
18+
*
19+
* @return string Path without the prefix or the original value.
20+
*/
21+
function remove_block_asset_path_prefix( $asset_handle_or_path ) {
22+
$path_prefix = 'file:';
23+
if ( strpos( $asset_handle_or_path, $path_prefix ) !== 0 ) {
24+
return $asset_handle_or_path;
25+
}
26+
return substr(
27+
$asset_handle_or_path,
28+
strlen( $path_prefix )
29+
);
30+
}
31+
32+
/**
33+
* Generates the name for an asset based on the name of the block
34+
* and the field name provided.
35+
*
36+
* @since 5.5.0
37+
*
38+
* @param string $block_name Name of the block.
39+
* @param string $field_name Name of the metadata field.
40+
*
41+
* @return string Generated asset name for the block's field.
42+
*/
43+
function generate_block_asset_handle( $block_name, $field_name ) {
44+
$field_mappings = array(
45+
'editorScript' => 'editor-script',
46+
'script' => 'script',
47+
'editorStyle' => 'editor-style',
48+
'style' => 'style',
49+
);
50+
return str_replace( '/', '-', $block_name ) .
51+
'-' . $field_mappings[ $field_name ];
52+
}
53+
54+
/**
55+
* Finds a script handle for the selected block metadata field. It detects
56+
* when a path to file was provided and finds a corresponding
57+
* asset file with details necessary to register the script under
58+
* automatically generated handle name. It returns unprocessed script handle
59+
* otherwise.
60+
*
61+
* @since 5.5.0
62+
*
63+
* @param array $metadata Block metadata.
64+
* @param string $field_name Field name to pick from metadata.
65+
*
66+
* @return string|boolean Script handle provided directly or created through
67+
* script's registration, or false on failure.
68+
*/
69+
function register_block_script_handle( $metadata, $field_name ) {
70+
if ( empty( $metadata[ $field_name ] ) ) {
71+
return false;
72+
}
73+
$script_handle = $metadata[ $field_name ];
74+
$script_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
75+
if ( $script_handle === $script_path ) {
76+
return $script_handle;
77+
}
78+
79+
$script_handle = generate_block_asset_handle( $metadata['name'], $field_name );
80+
$script_asset_path = realpath(
81+
dirname( $metadata['file'] ) . '/' .
82+
substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
83+
);
84+
if ( ! file_exists( $script_asset_path ) ) {
85+
$message = sprintf(
86+
/* translators: %1: field name. %2: block name */
87+
__( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.', 'default' ),
88+
$field_name,
89+
$metadata['name']
90+
);
91+
_doing_it_wrong( __FUNCTION__, $message, '5.5.0' );
92+
return false;
93+
}
94+
$script_asset = require( $script_asset_path );
95+
$result = wp_register_script(
96+
$script_handle,
97+
plugins_url( $script_path, $metadata['file'] ),
98+
$script_asset['dependencies'],
99+
$script_asset['version']
100+
);
101+
return $result ? $script_handle : false;
102+
}
103+
104+
/**
105+
* Finds a style handle for the block metadata field. It detects when a path
106+
* to file was provided and registers the style under automatically
107+
* generated handle name. It returns unprocessed style handle otherwise.
108+
*
109+
* @since 5.5.0
110+
*
111+
* @param array $metadata Block metadata.
112+
* @param string $field_name Field name to pick from metadata.
113+
*
114+
* @return string|boolean Style handle provided directly or created through
115+
* style's registration, or false on failure.
116+
*/
117+
function register_block_style_handle( $metadata, $field_name ) {
118+
if ( empty( $metadata[ $field_name ] ) ) {
119+
return false;
120+
}
121+
$style_handle = $metadata[ $field_name ];
122+
$style_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
123+
if ( $style_handle === $style_path ) {
124+
return $style_handle;
125+
}
126+
127+
$style_handle = generate_block_asset_handle( $metadata['name'], $field_name );
128+
$block_dir = dirname( $metadata['file'] );
129+
$result = wp_register_style(
130+
$style_handle,
131+
plugins_url( $style_path, $metadata['file'] ),
132+
array(),
133+
filemtime( realpath( "$block_dir/$style_path" ) )
134+
);
135+
return $result ? $style_handle : false;
136+
}
137+
12138
/**
13139
* Registers a block type from metadata stored in the `block.json` file.
14140
*
@@ -25,22 +151,72 @@
25151
* @return WP_Block_Type|false The registered block type on success, or false on failure.
26152
*/
27153
function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
28-
$file = ( substr( $file_or_folder, -10 ) !== 'block.json' ) ?
29-
trailingslashit( $file_or_folder ) . 'block.json' :
154+
$filename = 'block.json';
155+
$metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
156+
trailingslashit( $file_or_folder ) . $filename :
30157
$file_or_folder;
31-
if ( ! file_exists( $file ) ) {
158+
if ( ! file_exists( $metadata_file ) ) {
32159
return false;
33160
}
34161

35-
$metadata = json_decode( file_get_contents( $file ), true );
36-
if ( ! is_array( $metadata ) ) {
162+
$metadata = json_decode( file_get_contents( $metadata_file ), true );
163+
if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
37164
return false;
38165
}
166+
$metadata['file'] = $metadata_file;
167+
168+
$settings = array();
169+
$property_mappings = array(
170+
'title' => 'title',
171+
'category' => 'category',
172+
'parent' => 'parent',
173+
'icon' => 'icon',
174+
'description' => 'description',
175+
'keywords' => 'keywords',
176+
'attributes' => 'attributes',
177+
'supports' => 'supports',
178+
'styles' => 'styles',
179+
'example' => 'example',
180+
);
181+
182+
foreach ( $property_mappings as $key => $mapped_key ) {
183+
if ( isset( $metadata[ $key ] ) ) {
184+
$settings[ $mapped_key ] = $metadata[ $key ];
185+
}
186+
}
187+
188+
if ( ! empty( $metadata['editorScript'] ) ) {
189+
$settings['editor_script'] = register_block_script_handle(
190+
$metadata,
191+
'editorScript'
192+
);
193+
}
194+
195+
if ( ! empty( $metadata['script'] ) ) {
196+
$settings['script'] = register_block_script_handle(
197+
$metadata,
198+
'script'
199+
);
200+
}
201+
202+
if ( ! empty( $metadata['editorStyle'] ) ) {
203+
$settings['editor_style'] = register_block_style_handle(
204+
$metadata,
205+
'editorStyle'
206+
);
207+
}
208+
209+
if ( ! empty( $metadata['style'] ) ) {
210+
$settings['style'] = register_block_style_handle(
211+
$metadata,
212+
'style'
213+
);
214+
}
39215

40216
return register_block_type(
41217
$metadata['name'],
42218
array_merge(
43-
$metadata,
219+
$settings,
44220
$args
45221
)
46222
);

0 commit comments

Comments
 (0)