概述

iframe 集成是改进编辑体验持续努力的一部分。Gutenberg 插件始终在 iframe 内运行文章编辑器,并在此基础上构建了模板编辑器的原始 iframe 迁移功能。WordPress Core 正朝着相同的行为方向发展。

本指南鼓励将块迁移到 API 版本 3,以准备计划中的文章编辑器的 iframe 集成。它有助于提前验证块在 iframe 编辑器中的工作情况,并协助更新块以便它们在 iframe 环境中正常工作。

什么是 iframe 编辑器?

iframe 编辑器的优势

从技术角度来看,iframe 编辑器提供了几个重要优势:

  • 样式隔离:管理样式不再影响编辑器内容,消除了重置管理 CSS 规则的需要。内容样式不再影响管理屏幕,因此块和主题的 CSS 规则不再需要前缀。
  • 视口相对单位:视口相关的 CSS 单位(vw、vh)可以正常工作。编辑器内容的尺寸通常与管理页面的尺寸不同,因此如果没有 iframe,像 vw 这样的单位将相对于管理页面。
  • 媒体查询:媒体查询原生工作,无需使用不稳定的变通方法。
  • 更易于开发:块和主题作者受益,因为来自前端页面的样式可以几乎无需调整即可直接引入。这也适用于轻量级块,其编辑器 DOM 结构与前端相匹配。
  • 选择处理:由于编辑器内容在单独的窗口中,编辑器的选择保持可见的同时,也可以在编辑器 UI 中具有(折叠的)选择,例如 URL 输入字段。

带 iframe 的文章编辑器将通过减少样式冲突和提高布局准确性来为块和主题作者的生活带来便利。

何时文章编辑器作为 iframe 工作?

虽然大多数编辑器(包括模板编辑器)已经作为 iframe 工作,但 WordPress 7.0 保留了向后兼容的条件回退(由 useShouldIframe 钩子 确定):

  • 如果启用了 Gutenberg 插件:编辑器始终作为 iframe 工作。
  • 如果未启用 Gutenberg 插件:当满足以下任一条件时,编辑器作为 iframe 工作:设备类型不是 Desktop(例如,平板或移动预览),当前文章类型为 wp_template 或 wp_block,已激活缩小模式,或者帖子内容中存在的所有块的 apiVersion 均为 3 或更高。

在未启用 Gutenberg 插件的 WordPress 7.0 中,如果块在帖子中存在且其 apiVersion 为 2,可能会触发非 iframe 回退。这仅是一个临时兼容性路径:当启用了 Gutenberg 插件时不适用,并计划在 WordPress 7.1 中移除。块应在在 iframe 编辑器中进行测试后迁移到 apiVersion 3。

何时文章编辑器将作为 iframe 工作?

在 Gutenberg 23.6 和 WordPress 7.1 中,无论帖子内容中的块的 apiVersion 如何,文章编辑器始终作为 iframe 工作。移除了对非 iframe 编辑器的条件回退,因此 apiVersion 为 2 或更低的块始终在 iframe 内运行。

在此之前,为了鼓励开发者在 iframe 编辑器中进行测试,WordPress 6.9 引入了当使用 apiVersion 2 或更低版本注册块时的浏览器控制台警告,并更新了 block.json 架构,仅允许 apiVersion: 3。有关详细信息,请参阅 为文章编辑器准备完整的 iframe 集成。

在 WordPress 7.0 中,iframe 条件是针对实际插入到帖子内容中的块的 apiVersion 进行评估,而不是针对所有注册的块。仍然不强制使用 iframe:如果内容中存在 apiVersion 为 2 或更低的块,文章编辑器将回退到非 iframe 编辑器。有关详细信息,请参阅 WordPress 7.0 中的带 iframe 的编辑器更改。

如何在 iframe 文章编辑器中测试您的块

所有核心块都已使用 apiVersion 3。在 iframe 编辑器中测试您的块后,将其更新为 apiVersion 3。

最简单的测试方法是使用 Gutenberg 23.6 或更高版本,其中文章编辑器始终使用 iframe。在未启用 Gutenberg 插件的 WordPress 7.0 中,如果帖子包含使用 apiVersion 2 或更低版本的块,编辑器可能会回退到非 iframe 编辑器。

iframe 编辑器的技术考虑因素

大多数块应该无需修改即可在 iframe 编辑器中工作,但以下技术考虑因素和需要注意的事项在下面进行了记录。

文档和窗口

iframe 将具有与管理页面不同的 document 和 window,管理页面现在是父窗口。编辑器脚本加载在管理页面中,因此访问 document 或 window 对内容进行操作将不再有效。

大多数用 React 编写的块应继续正常工作,除非您依赖 document 或 window。要修复,您需要创建一个引用以访问相对文档(ownerDocument)或窗口(defaultView)。无论是否有 iframe,这都是良好的做法,可以避免使用全局变量。

使用 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>
    );
}

使用 useRefEffect(推荐)

如果您附加事件处理程序,请记住,如果引用发生变化,useEffect 回调将不会被调用,因此最好使用新的 useRefEffect API,该 API 在引用发生变化的情况下(除了任何传递的依赖项外)将调用给定的回调。

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

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>
    );
}

其他框架和库

对于编辑器,诸如 jQuery 之类的脚本加载在父窗口(管理页面)中,这是可以的。当使用这些与 iframe 中的块交互时,您应该传递元素引用。

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/compose';
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>
    );
}

如果库使用全局 window 或 document 且您无法控制怎么办?

提交问题或 PR 给该库,要求使用 ownerDocument 和 defaultView 代替全局变量。理想情况下,任何库都应允许使用 iframe 中的元素作为目标进行初始化。这绝非不可能。请随时联系我们以提及该问题。

在此期间,您可以使用加载在 iframe 内部的脚本。我们已在 iframe 中加载了所有前端脚本来修复这些情况,但请注意,理想情况下您不应使用任何加载在 iframe 中的脚本。您可以使用 defaultView 访问脚本。

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

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

        // 使用 iframe 中加载的脚本。
        // 脚本是异步加载的,因此需要检查脚本是否已加载。
        // 依赖项加载完成后,区块将重新渲染。
        if ( ! defaultView.jQuery ) {
            return;
        }

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

    const blockProps = useBlockProps( { ref } );

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

修补库

当外部库引用全局 document 或 window,且您无法等待上游修复时,您可以应用自己的补丁使其兼容 iframe。以下步骤使用 patch-package 将补丁纳入版本控制(假设使用 npm):

  1. 直接在 node_modules 中编辑库的代码以使其正常工作,将全局 document / window 引用替换为从区块元素派生的 ownerDocument / defaultView。例如,对于 @panzoom/panzoom,请编辑 node_modules/@panzoom/panzoom/dist/panzoom.es.js。
  2. 安装 patch-package:
    bash
    npm add -D patch-package
  3. 为库生成补丁:
    bash
    npm exec --no -- patch-package @panzoom/panzoom
  4. 在您的 package.json 中添加 postinstall 脚本,以便每次安装后自动应用补丁:
    json
    "scripts": {
    "postinstall": "patch-package"
    }
  5. 将生成的 .patch 文件(位于 patches 目录中)和更新的 package.json 提交到版本控制。

补丁通常将全局引用替换为元素的 ownerDocument。例如,@panzoom/panzoom 的补丁如下所示:

 function isAttached(node) {
+    var { ownerDocument } = node;
     var currentNode = node;
     while (currentNode && currentNode.parentNode) {
-        if (currentNode.parentNode === document)
+        if (currentNode.parentNode === ownerDocument)
             return true;
         currentNode =
             currentNode.parentNode instanceof ShadowRoot
@@ function Panzoom(elem, options) {
         bound = true;
+        var { ownerDocument } = elem;
         onPointer('down', options.canvas ? parent : elem, handleDown);
-        onPointer('move', document, handleMove, { passive: true });
-        onPointer('up', document, handleUp, { passive: true });
+        onPointer('move', ownerDocument, handleMove, { passive: true });
+        onPointer('up', ownerDocument, handleUp, { passive: true });
     }

即使补丁有效,也请优先提交上游问题或 PR,以便库原生支持 iframe 环境。