块编辑器开发文档

为 iframe 编辑器兼容性迁移区块

💡 云策文档标注

概述

本文档介绍了 WordPress 编辑器向 iframe 集成迁移的背景和必要性,旨在帮助开发者将区块迁移至 API 版本 3,以确保在 iframe 编辑器中的兼容性。它涵盖了 iframe 编辑器的技术优势、启用条件、测试方法以及迁移时的技术注意事项。

关键要点

  • iframe 编辑器提供样式隔离、视口相对单位正确工作、媒体查询原生支持等优势,减少样式冲突并提高布局准确性。
  • 文章编辑器在满足特定条件时以 iframe 运行:启用 Gutenberg 插件时,需主题为区块主题或所有注册区块为 apiVersion 3 或更高;未启用时,需所有注册区块为 apiVersion 3 或更高。
  • WordPress 6.9 引入了开发者警告和 block.json 模式更新,鼓励迁移至 apiVersion 3;WordPress 7.0 计划强制所有编辑器以 iframe 运行。
  • 测试区块在 iframe 编辑器中的兼容性:将区块 apiVersion 设置为 3,并确保无其他第三方区块使用版本 2 或更低。
  • 技术注意事项:iframe 具有独立的 document 和 window,需使用 ref 访问 ownerDocument 和 defaultView,推荐使用 useRefEffect API 处理事件。

代码示例

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/element';

export default function Edit() {
    const ref = useRefEffect( ( element ) => {
        const { ownerDocument } = element;
        const { defaultView } = ownerDocument;
        defaultView.addEventListener( ... );
        return () => {
            defaultView.removeEventListener( ... );
        };
    }, [] );

    const blockProps = useBlockProps( { ref } );

    return (
        <div { ...blockProps }>
            Hello world!
        </div>
    );
}

注意事项

  • 对于依赖全局 window 或 document 的第三方库,建议提交 issue 或 PR 以使用 ownerDocument 和 defaultView;临时解决方案可使用 iframe 中加载的脚本,但应避免依赖。
  • 使用 jQuery 等库时,需传递元素引用以确保在 iframe 中正确交互。

📄 原文内容

Overview

The iframe integration is part of an ongoing effort to modernize the editing experience. WordPress is moving toward running the post editor inside an iframe, building upon the original iframe migration in the template editor.

This guide encourages migration of blocks to API version 3 in preparation for the planned iframe integration of the post editor. It helps verify in advance that blocks work in the iframe editor and assists in updating blocks so they work correctly in the iframe environment.

What is the iframe editor?

Benefits of the iframe editor

From a technical perspective, the iframe editor provides several important benefits:

  • Style isolation: Admin styles no longer affect the editor content, eliminating the need to reset admin CSS rules. Content styles no longer affect the admin screen, so block and theme CSS rules no longer need to be prefixed.
  • Viewport-relative units: Viewport-relative CSS units (vw, vh) work correctly. The dimensions of the editor content are usually not the same as the dimensions of the admin page, so without an iframe, units like vw would be relative to the admin page.
  • Media queries: Media queries work natively without needing workarounds, which were fragile.
  • Easier development: Block and theme authors benefit because styles from the front-end can be dropped in with very little, if anything, to adjust. This also applies to lighter blocks, where the editor DOM structure matches the front-end.
  • Selection handling: With a separate window for the editor content, it’s possible for the selection in the editor to remain visible while also having a (collapsed) selection in the editor UI, for example an input field for a URL.

The iframed post editor will make life easier for block and theme authors by reducing styling conflicts and improving layout accuracy.

When does the post editor work as an iframe?

While most editors, including the template editor, already work as iframes, for backward compatibility, the current post editor only works as an iframe when the following conditions are met (determined by the useShouldIframe hook):

  • If the Gutenberg plugin is enabled:: The active theme is a block-based theme OR all registered blocks have apiVersion 3 or higher
  • If the Gutenberg plugin is not enabled:: All registered blocks have apiVersion 3 or higher

In summary, if you haven’t been able to fully test your blocks in the iframe editor yet, by maintaining apiVersion 2, you can prevent the post editor from working as an iframe in most cases. Once you’ve confirmed that your blocks work in the iframe editor, you can then migrate to apiVersion 3.

When will the post editor work as an iframe?

In WordPress 7.0, the post editor is planned to always work as an iframe, regardless of the apiVersion of registered blocks.

Ahead of this, to encourage developers to test in the iframe editor, WordPress 6.9 introduces the following developer warnings and schema changes:

  • Browser console warnings: When blocks are registered with apiVersion 2 or lower, WordPress displays the following message in the browser console:
    Block with API version 2 or lower is deprecated since version 6.9. See: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/block-migration-for-iframe-editor-compatibility/<br />
    Note: The block "my-plugin/my-block" is registered with API version 2. This means that the post editor may work as a non-iframe editor. Since all editors are planned to work as iframes in the future, set the `apiVersion` field to 3 and test the block inside the iframe editor.
  • block.json schema update: The block.json schema has been updated to only allow apiVersion: 3 for new or updated blocks. Older versions (1 or 2) will no longer pass schema validation.

How to test your blocks in the iframe post editor

All core blocks are already using apiVersion 3, so simply changing your apiVersion to 3 should allow your blocks to work in the iframe post editor.

However, make sure that no other third-party blocks registered with version 2 or lower are present. If blocks with version 2 or lower are registered, the post editor may not work as an iframe editor.

Technical considerations for the iframe editor

Most blocks should work in the iframe editor without modification, but the following technical considerations and things to be aware of are documented below.

Document and window

The iframe will have a different document and window than the admin page, which is now the parent window. Editor scripts are loaded in the admin page, so accessing the document or window to do something with the content will no longer work.

Most blocks written in React should continue to work properly, except if you rely on document or window. To fix, you need to create a ref to access the relative document (ownerDocument) or window (defaultView). Regardless of the iframe, it is good practice to do this and avoid the use of globals.

Using useRef

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRef, useEffect } from '@wordpress/element';

export default function Edit() {
    const ref = useRef();

    useEffect( () => {
        const { ownerDocument } = ref.current;
        const { defaultView } = ownerDocument;
        defaultView.addEventListener( ... );
        return () => {
            defaultView.removeEventListener( ... );
        };
    }, [] );

    const blockProps = useBlockProps( { ref } );

    return (
        <div { ...blockProps }>
            Hello world!
        </div>
    );
}

Using useRefEffect (recommended)

If you attach event handlers, remember that the useEffect callback will not be called if the ref changes, so it is good practice to use the new useRefEffect API, which will call the given callback if the ref changes in addition to any dependencies passed.

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/element';

export default function Edit() {
    const ref = useRefEffect( ( element ) => {
        const { ownerDocument } = element;
        const { defaultView } = ownerDocument;
        defaultView.addEventListener( ... );
        return () => {
            defaultView.removeEventListener( ... );
        };
    }, [] );

    const blockProps = useBlockProps( { ref } );

    return (
        <div { ...blockProps }>
            Hello world!
        </div>
    );
}

Other frameworks and libraries

For the editor, scripts such as jQuery are loaded in the parent window (admin page), which is fine. When using these to interact with a block in the iframe, you should pass the element reference.

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/element';
import jQuery from 'jquery';

export default function Edit() {
    const ref = useRefEffect( ( element ) => {
        jQuery( element ).masonry( … );
        return () => {
            jQuery( element ).masonry( 'destroy' );
        }
    }, [] );

    const blockProps = useBlockProps( { ref } );

    return (
        <div { ...blockProps }>
            Hello world!
        </div>
    );
}

What if the library uses global window or document and it’s out of your control?

Submit an issue or PR for the library to use ownerDocument and defaultView instead of the globals. Ideally, any library should allow initialization with an element in an iframe as the target. It’s never impossible. Feel free to contact us to mention the issue.

In the meantime, you can use the script that is loaded inside the iframe. We’ve loaded all front-end scripts in the iframe to fix these cases, but note that ideally you shouldn’t use scripts loaded in the iframe at all. You can use defaultView to access the script.

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/element';
import jQuery from 'jquery';

export default function Edit() {
    const ref = useRefEffect( ( element ) => {
        const { ownerDocument } = element;
        const { defaultView } = ownerDocument;

        // Use the script loaded in the iframe.
        // Scripts are loaded asynchronously, so check if the script is loaded.
        // After the dependencies have loaded, the block will re-render.
        if ( ! defaultView.jQuery ) {
            return;
        }

        defaultView.jQuery( element ).masonry( … );
        return () => {
            defaultView.jQuery( element ).masonry( 'destroy' );
        }
    } );

    const blockProps = useBlockProps( { ref } );

    return (
        <div { ...blockProps }>
            Hello world!
        </div>
    );
}