阐述 WordPress `register_block_type_from_metadata()` 函数的源码:如何从 `block.json` 自动注册区块。

各位朋友,早上好!欢迎来到今天的“WordPress 区块开发实战讲座”。今天咱们要聊的是一个非常方便的函数:register_block_type_from_metadata()。这玩意儿就像一个魔法棒,能帮你从 block.json 文件里“嗖”的一下,自动注册一个 WordPress 区块。

咱们先来聊聊,为啥需要这么个东西?

过去的日子:手动注册区块的苦

register_block_type_from_metadata() 出现之前,注册一个区块,那叫一个“累觉不爱”。你得手动编写 PHP 代码,告诉 WordPress 区块的名字、属性、编辑和保存函数等等。就像这样:

<?php
function my_custom_block_init() {
    register_block_type( 'my-plugin/my-block', array(
        'attributes'      => array(
            'content' => array(
                'type' => 'string',
                'default' => 'Hello World!',
            ),
        ),
        'render_callback' => 'my_custom_block_render',
    ) );
}
add_action( 'init', 'my_custom_block_init' );

function my_custom_block_render( $attributes ) {
    $content = isset( $attributes['content'] ) ? $attributes['content'] : '';
    return '<p>' . esc_html( $content ) . '</p>';
}

你看,光是定义一个简单的区块,就得写这么多代码。如果区块稍微复杂点,属性多了,渲染逻辑也复杂了,那代码量简直要爆炸。而且,这些代码散落在不同的地方,维护起来也很麻烦。

block.json:区块定义的救星

block.json 的出现,简直就是黑暗中的一道曙光。它允许你用 JSON 格式来定义区块的所有信息,包括名称、属性、标题、描述、图标、关键词等等。

一个简单的 block.json 文件可能长这样:

{
  "name": "my-plugin/my-block",
  "title": "My Custom Block",
  "description": "A simple custom block.",
  "category": "common",
  "icon": "smiley",
  "keywords": [ "custom", "block" ],
  "attributes": {
    "content": {
      "type": "string",
      "default": "Hello World!"
    }
  },
  "supports": {
    "align": true
  },
  "textdomain": "my-plugin"
}

这个 block.json 文件,就包含了我们之前手动注册区块时需要的所有信息。而且,JSON 格式的可读性非常好,也方便维护。

register_block_type_from_metadata():自动化注册的利器

有了 block.json 文件,我们就可以使用 register_block_type_from_metadata() 函数,来自动注册区块了。这个函数会读取 block.json 文件,然后根据文件中的信息,自动调用 register_block_type() 函数来注册区块。

使用起来非常简单:

<?php
function my_custom_block_init() {
    register_block_type_from_metadata( __DIR__ );
}
add_action( 'init', 'my_custom_block_init' );

只需要传入 block.json 文件所在的目录即可。__DIR__ 是一个 PHP 魔术常量,表示当前文件的目录。

register_block_type_from_metadata() 源码解析

好了,铺垫了这么多,咱们终于要进入正题了。让我们一起来深入了解一下 register_block_type_from_metadata() 函数的源码,看看它是如何工作的。

register_block_type_from_metadata() 函数的定义位于 wp-includes/block-library.php 文件中。咱们来看一段简化后的代码(为了方便讲解,去掉了错误处理和一些细节):

<?php
function register_block_type_from_metadata( $path ) {
    $metadata_file = trailingslashit( $path ) . 'block.json';

    if ( ! file_exists( $metadata_file ) ) {
        return false; // 或者抛出异常,这里简化处理
    }

    $metadata = json_decode( file_get_contents( $metadata_file ), true );

    if ( ! is_array( $metadata ) ) {
        return false; // 或者抛出异常,这里简化处理
    }

    // 确保 'name' 键存在
    if ( ! isset( $metadata['name'] ) ) {
        return false; // 或者抛出异常,这里简化处理
    }

    // 构建注册参数
    $args = array();

    // 处理 render 回调
    if ( isset( $metadata['render'] ) ) { // 注意这里是 'render' 而不是 'render_callback'
        $render_file = trailingslashit( $path ) . $metadata['render'];

        if ( file_exists( $render_file ) ) {
            $args['render_callback'] = function( $attributes, $content, $block ) use ( $render_file ) {
                ob_start();
                include( $render_file );
                return ob_get_clean();
            };
        }
    }

    // 处理编辑脚本和样式
    if ( isset( $metadata['editorScript'] ) ) {
        $args['editor_script'] = $metadata['editorScript'];
    }

    if ( isset( $metadata['editorStyle'] ) ) {
        $args['editor_style'] = $metadata['editorStyle'];
    }

    if ( isset( $metadata['style'] ) ) {
        $args['style'] = $metadata['style'];
    }

    // 其他属性,直接从 metadata 复制
    $keys_to_copy = array(
        'attributes',
        'supports',
        'provides_context',
        'uses_context',
        'viewScript', //注意这里是 'viewScript' 而不是 'view_script'
        'textdomain',
    );

    foreach ( $keys_to_copy as $key ) {
        if ( isset( $metadata[ $key ] ) ) {
            $args[ $key ] = $metadata[ $key ];
        }
    }

    // 注册区块
    register_block_type( $metadata['name'], $args );

    return true;
}

咱们来一行一行地分析一下这段代码:

  1. $metadata_file = trailingslashit( $path ) . 'block.json';: 首先,它会根据传入的 $path 参数,拼接出 block.json 文件的完整路径。trailingslashit() 函数的作用是在路径末尾添加一个斜杠,确保路径的正确性。

  2. if ( ! file_exists( $metadata_file ) ) { ... }: 接下来,它会检查 block.json 文件是否存在。如果不存在,就直接返回 false

  3. $metadata = json_decode( file_get_contents( $metadata_file ), true );: 如果 block.json 文件存在,它会读取文件的内容,并使用 json_decode() 函数将其解码成一个 PHP 数组。true 参数表示将 JSON 对象解码成关联数组。

  4. if ( ! is_array( $metadata ) ) { ... }: 然后,它会检查解码后的 $metadata 是否是一个数组。如果不是,就说明 block.json 文件的格式有问题,直接返回 false

  5. if ( ! isset( $metadata['name'] ) ) { ... }: 接下来,它会检查 $metadata 数组中是否存在 name 键。name 键是区块的名称,也是必须存在的。如果不存在,就返回 false

  6. $args = array();: 创建一个空数组 $args,用于存储传递给 register_block_type() 函数的参数。

  7. if ( isset( $metadata['render'] ) ) { ... }: 这里处理了 render 属性。注意,block.json 里面写的是 render,函数内部处理的是 render_callback。 如果 block.json 中定义了 render 属性,它会认为这是一个 PHP 文件的路径,并使用 include() 函数来加载该文件,并将其输出作为区块的渲染结果。 这里使用了一个匿名函数作为 render_callback,这个匿名函数会使用 ob_start()ob_get_clean() 函数来捕获 include() 函数的输出。

  8. if ( isset( $metadata['editorScript'] ) ) { ... } if ( isset( $metadata['editorStyle'] ) ) { ... } if ( isset( $metadata['style'] ) ) { ... }: 这三段代码分别处理了 editorScripteditorStylestyle 属性。这些属性分别指定了区块的编辑脚本、编辑样式和前端样式。

  9. $keys_to_copy = array( ... );: 创建一个数组 $keys_to_copy,包含了需要从 $metadata 数组中复制到 $args 数组中的键名。这些键名包括 attributessupportsprovides_contextuses_contexttextdomain

  10. foreach ( $keys_to_copy as $key ) { ... }: 遍历 $keys_to_copy 数组,将 $metadata 数组中对应的键值复制到 $args 数组中。

  11. register_block_type( $metadata['name'], $args );: 最后,调用 register_block_type() 函数,注册区块。$metadata['name'] 是区块的名称,$args 是包含了区块各种属性的数组。

  12. return true;: 注册成功,返回 true

block.json 属性与 register_block_type() 参数的对应关系

咱们来整理一下 block.json 文件中的属性与 register_block_type() 函数参数的对应关系:

block.json 属性 register_block_type() 参数 说明
name (required) 区块名称 区块的唯一标识符,必须是 namespace/block-name 的格式。
title 区块的标题,显示在区块编辑器中。
description 区块的描述,显示在区块编辑器中。
category 区块的分类,用于在区块编辑器中组织区块。
icon 区块的图标,显示在区块编辑器中。
keywords 区块的关键词,用于在区块编辑器中搜索区块。
attributes attributes 区块的属性,用于存储区块的数据。
supports supports 区块支持的功能,例如对齐方式、颜色、字体大小等等。
provides_context provides_context 区块提供的上下文,用于与其他区块共享数据。
uses_context uses_context 区块使用的上下文,用于从其他区块获取数据。
render render_callback 一个 PHP 文件的路径,用于渲染区块的前端内容。
editorScript editor_script 区块的编辑脚本,用于在区块编辑器中控制区块的行为。
editorStyle editor_style 区块的编辑样式,用于在区块编辑器中控制区块的外观。
style style 区块的前端样式,用于在前端页面中控制区块的外观。
textdomain textdomain 区块的文本域,用于国际化和本地化。
viewScript view_script 用于增强区块前端行为的脚本。注意在block.json中使用的名称是viewScript ,而在register_block_type() 中,正确的参数名称是 view_script。 这个脚本通常用于添加交互性或动态功能,仅在区块的前端渲染时加载。

注意事项

  • render vs render_callback: block.json 中使用的是 render 属性,但在 register_block_type() 函数中,对应的参数是 render_callbackregister_block_type_from_metadata() 函数内部会处理这个转换。
  • 路径问题: rendereditorScripteditorStylestyle 属性的值,都应该是相对于 block.json 文件所在目录的路径。
  • 错误处理: 在实际开发中,你需要添加更完善的错误处理机制,例如检查文件是否存在、JSON 格式是否正确等等。
  • 安全性: 如果你的 render 文件中包含了用户输入的数据,一定要进行适当的转义,防止 XSS 攻击。
  • viewScript 属性:请注意在block.json中使用的名称是viewScript ,而在register_block_type() 中,正确的参数名称是 view_script

实战演练

咱们来做一个简单的实战演练,创建一个自定义区块,显示一段文本。

  1. 创建 block.json 文件:

    {
      "name": "my-plugin/text-block",
      "title": "Text Block",
      "description": "A simple block to display text.",
      "category": "common",
      "icon": "text",
      "attributes": {
        "text": {
          "type": "string",
          "default": "Hello World!"
        }
      },
      "editorScript": "build/index.js",
      "style": "build/style-index.css",
      "viewScript": "build/view.js"
    }
  2. 创建 index.js 文件 (编辑脚本):

    import { registerBlockType } from '@wordpress/blocks';
    import { useBlockProps, RichText } from '@wordpress/block-editor';
    
    registerBlockType( 'my-plugin/text-block', {
        edit: ( { attributes, setAttributes } ) => {
            const { text } = attributes;
            return (
                <RichText
                    { ...useBlockProps() }
                    tagName="p"
                    value={ text }
                    onChange={ ( newText ) => setAttributes( { text: newText } ) }
                    placeholder="Enter text here..."
                />
            );
        },
        save: ( { attributes } ) => {
            const { text } = attributes;
            return (
                <p { ...useBlockProps.save() }>
                    { text }
                </p>
            );
        },
    } );
  3. 创建 style-index.css 文件 (前端样式):

    .wp-block-my-plugin-text-block {
      font-size: 16px;
      color: #333;
    }
  4. 创建 view.js 文件 (前端增强脚本):

    // view.js
    document.addEventListener('DOMContentLoaded', function() {
        const textBlocks = document.querySelectorAll('.wp-block-my-plugin-text-block');
    
        textBlocks.forEach(block => {
            block.addEventListener('click', function() {
                alert('You clicked the text block!');
            });
        });
    });
  5. 创建 PHP 文件来注册区块:

    <?php
    function my_custom_block_init() {
        register_block_type_from_metadata( plugin_dir_path( __FILE__ ) );
    }
    add_action( 'init', 'my_custom_block_init' );
  6. 构建脚本:

    确保你已经安装了 @wordpress/scripts。如果没有,请运行:

    npm install @wordpress/scripts --save-dev

    然后在你的 package.json 文件中添加以下脚本:

    {
      "scripts": {
        "build": "wp-scripts build"
      }
    }

    然后运行:

    npm run build

    这会在你的区块目录中创建一个 build 目录,其中包含编译后的 JavaScript 和 CSS 文件。

  7. 激活插件: 将包含这些文件的文件夹打包成一个 WordPress 插件,并激活它。

现在,你就可以在 WordPress 编辑器中使用这个自定义区块了。

总结

register_block_type_from_metadata() 函数是一个非常方便的工具,可以帮助你从 block.json 文件中自动注册 WordPress 区块。它简化了区块注册的过程,提高了开发效率,也让代码更加清晰易懂。 掌握了这个函数,你就可以更加轻松地开发自定义区块,扩展 WordPress 的功能。

希望今天的讲座对你有所帮助! 感谢大家的聆听,祝大家编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注