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

Block API: Extend register_block_type_from_metadata to handle assets #22519

Merged
merged 14 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions docs/rfc/block-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This RFC is intended to serve both as a specification and as documentation for t

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.

In more practical terms, an implementation should fulfill requirements that...
In more practical terms, an implementation should fulfill requirements that:

* 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:
* Fetching the available block types through REST APIs.
Expand All @@ -16,7 +16,7 @@ In more practical terms, an implementation should fulfill requirements that...

It can statically analyze the files of any plugin to retrieve blocks and their properties.
* 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.
* 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).
* 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).

## References

Expand Down Expand Up @@ -67,7 +67,7 @@ To register a new block type, start by creating a `block.json` file. This file:
"category": "text",
"parent": [ "core/group" ],
"icon": "star",
"description": "Shows warning, error or success notices ...",
"description": "Shows warning, error or success notices",
"keywords": [ "alert", "message" ],
"textdomain": "my-plugin",
"attributes": {
Expand All @@ -90,10 +90,10 @@ To register a new block type, start by creating a `block.json` file. This file:
"message": "This is a notice!"
},
},
"editorScript": "build/editor.js",
"script": "build/main.js",
"editorStyle": "build/editor.css",
"style": "build/style.css"
"editorScript": "file:./build/index.js",
"script": "file:./build/script.js",
"editorStyle": "file:./build/index.css",
"style": "file:./build/style.css"
}
```

Expand Down Expand Up @@ -295,7 +295,7 @@ Plugins and Themes can also register [custom block style](/docs/designers-develo
"example": {
"attributes": {
"message": "This is a notice!"
},
}
}
}
```
Expand All @@ -312,7 +312,7 @@ Plugins and Themes can also register [custom block style](/docs/designers-develo
* Property: `editorScript`

```json
{ "editorScript": "build/editor.js" }
{ "editorScript": "file:./build/index.js" }
```

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

```json
{ "script": "build/main.js" }
{ "script": "file:./build/script.js" }
```

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

```json
{ "editorStyle": "build/editor.css" }
{ "editorStyle": "file:./build/index.css" }
```

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

```json
{ "style": "build/style.css" }
{ "style": "file:./build/style.css" }
```

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

### `WPDefinedAsset`

The `WPDefinedAsset` type is a subtype of string, where the value must represent an absolute or relative path to a JavaScript or CSS file.
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.

An alternative would be a script or style handle name referencing a registered asset using WordPress helpers.

**Example:**

In `block.json`:
```json
{ "editorScript": "build/editor.js" }
{
"editorScript": "file:./build/index.js",
"editorStyle": "my-editor-style-handle"
}
```

#### WordPress context

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.
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.

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.

Expand All @@ -419,7 +424,7 @@ build/

In `block.json`:
```json
{ "editorScript": "build/index.js" }
{ "editorScript": "file:./build/index.js" }
```

In `build/index.asset.php`:
Expand Down Expand Up @@ -480,7 +485,7 @@ Implementation should follow the existing [get_plugin_data](https://codex.wordpr
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.

This function takes two params:
- `$path` (`string`) – path to the folder where the `block.json` file is located.
- `$path` (`string`) – path to the folder where the `block.json` file is located or full path to the metadata file if named differently.
- `$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:
- `$render_callback` (`callable`) – callback used to render blocks of this block type.

Expand Down
188 changes: 182 additions & 6 deletions lib/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,132 @@
*/

if ( ! function_exists( 'register_block_type_from_metadata' ) ) {
/**
* Removes the block asset's path prefix if provided.
*
* @since 5.5.0
*
* @param string $asset_handle_or_path Asset handle or prefixed path.
*
* @return string Path without the prefix or the original value.
*/
function remove_block_asset_path_prefix( $asset_handle_or_path ) {
$path_prefix = 'file:';
if ( strpos( $asset_handle_or_path, $path_prefix ) !== 0 ) {
return $asset_handle_or_path;
}
return substr(
$asset_handle_or_path,
strlen( $path_prefix )
);
}

/**
* Generates the name for an asset based on the name of the block
* and the field name provided.
*
* @since 5.5.0
*
* @param string $block_name Name of the block.
* @param string $field_name Name of the metadata field.
*
* @return string Generated asset name for the block's field.
*/
function generate_block_asset_handle( $block_name, $field_name ) {
$field_mappings = array(
'editorScript' => 'editor-script',
'script' => 'script',
'editorStyle' => 'editor-style',
'style' => 'style',
);
return str_replace( '/', '-', $block_name ) .
'-' . $field_mappings[ $field_name ];
}

/**
* Finds a script handle for the selected block metadata field. It detects
* when a path to file was provided and finds a corresponding
* asset file with details necessary to register the script under
* automatically generated handle name. It returns unprocessed script handle
* otherwise.
*
* @since 5.5.0
*
* @param array $metadata Block metadata.
* @param string $field_name Field name to pick from metadata.
*
* @return string|boolean Script handle provided directly or created through
* script's registration, or false on failure.
*/
function register_block_script_handle( $metadata, $field_name ) {
if ( empty( $metadata[ $field_name ] ) ) {
return false;
}
$script_handle = $metadata[ $field_name ];
$script_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
if ( $script_handle === $script_path ) {
return $script_handle;
}

$script_handle = generate_block_asset_handle( $metadata['name'], $field_name );
$script_asset_path = realpath(
dirname( $metadata['file'] ) . '/' .
substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
);
if ( ! file_exists( $script_asset_path ) ) {
$message = sprintf(
/* translators: %1: field name. %2: block name */
__( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.', 'default' ),
$field_name,
$metadata['name']
);
_doing_it_wrong( __FUNCTION__, $message, '5.5.0' );
return false;
}
$script_asset = require( $script_asset_path );
$result = wp_register_script(
$script_handle,
plugins_url( $script_path, $metadata['file'] ),
$script_asset['dependencies'],
$script_asset['version']
);
return $result ? $script_handle : false;
}

/**
* Finds a style handle for the block metadata field. It detects when a path
* to file was provided and registers the style under automatically
* generated handle name. It returns unprocessed style handle otherwise.
*
* @since 5.5.0
*
* @param array $metadata Block metadata.
* @param string $field_name Field name to pick from metadata.
*
* @return string|boolean Style handle provided directly or created through
* style's registration, or false on failure.
*/
function register_block_style_handle( $metadata, $field_name ) {
if ( empty( $metadata[ $field_name ] ) ) {
return false;
}
$style_handle = $metadata[ $field_name ];
$style_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
if ( $style_handle === $style_path ) {
return $style_handle;
}

$style_handle = generate_block_asset_handle( $metadata['name'], $field_name );
$block_dir = dirname( $metadata['file'] );
$result = wp_register_style(
$style_handle,
plugins_url( $style_path, $metadata['file'] ),
array(),
filemtime( realpath( "$block_dir/$style_path" ) )
);
return $result ? $style_handle : false;
}

/**
* Registers a block type from metadata stored in the `block.json` file.
*
Expand All @@ -25,22 +151,72 @@
* @return WP_Block_Type|false The registered block type on success, or false on failure.
*/
function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
$file = ( substr( $file_or_folder, -10 ) !== 'block.json' ) ?
trailingslashit( $file_or_folder ) . 'block.json' :
$filename = 'block.json';
$metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
trailingslashit( $file_or_folder ) . $filename :
$file_or_folder;
if ( ! file_exists( $file ) ) {
if ( ! file_exists( $metadata_file ) ) {
return false;
}

$metadata = json_decode( file_get_contents( $file ), true );
if ( ! is_array( $metadata ) ) {
$metadata = json_decode( file_get_contents( $metadata_file ), true );
if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
return false;
}
$metadata['file'] = $metadata_file;

$settings = array();
$property_mappings = array(
'title' => 'title',
'category' => 'category',
Copy link
Member

Choose a reason for hiding this comment

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

fix was just to add a property for context here. see #23180

Copy link
Member Author

Choose a reason for hiding this comment

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

We also need to add providesContext there.

'parent' => 'parent',
'icon' => 'icon',
'description' => 'description',
'keywords' => 'keywords',
'attributes' => 'attributes',
'supports' => 'supports',
'styles' => 'styles',
gziolo marked this conversation as resolved.
Show resolved Hide resolved
'example' => 'example',
);

foreach ( $property_mappings as $key => $mapped_key ) {
if ( isset( $metadata[ $key ] ) ) {
$settings[ $mapped_key ] = $metadata[ $key ];
}
}

if ( ! empty( $metadata['editorScript'] ) ) {
$settings['editor_script'] = register_block_script_handle(
$metadata,
'editorScript'
);
}

if ( ! empty( $metadata['script'] ) ) {
$settings['script'] = register_block_script_handle(
$metadata,
'script'
);
}

if ( ! empty( $metadata['editorStyle'] ) ) {
$settings['editor_style'] = register_block_style_handle(
$metadata,
'editorStyle'
);
}

if ( ! empty( $metadata['style'] ) ) {
$settings['style'] = register_block_style_handle(
$metadata,
'style'
);
}

return register_block_type(
$metadata['name'],
array_merge(
$metadata,
$settings,
$args
)
);
Expand Down
Loading