跳转到主要内容

迁移区块以实现 iframe 编辑器兼容性

概述

iframe 集成是现代化编辑体验持续努力的一部分。在模板编辑器原始 iframe 迁移的基础上,WordPress 正朝着在 iframe 内运行文章编辑器的方向发展。

本指南鼓励迁移区块至 API 版本 3,为文章编辑器计划的 iframe 集成做准备。它有助于提前验证区块在 iframe 编辑器中的工作状态,并协助更新区块以确保其在 iframe 环境中正常运行。

什么是 iframe 编辑器?

iframe 编辑器的优势

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

  • 样式隔离:管理后台样式不再影响编辑器内容,无需重置管理后台 CSS 规则。内容样式不再影响管理后台界面,因此区块和主题的 CSS 规则不再需要添加前缀。
  • 视口相对单位:视口相对 CSS 单位(vw、vh)能正确工作。编辑器内容的尺寸通常与管理后台页面尺寸不同,因此若无 iframe,像 vw 这样的单位将相对于管理后台页面计算。
  • 媒体查询:媒体查询能原生工作,无需脆弱的变通方案。
  • 更易开发:区块和主题开发者受益,因为前端样式几乎无需调整即可直接使用。这也适用于轻量级区块,其编辑器 DOM 结构与前端匹配。
  • 选区处理:由于编辑器内容位于独立窗口,编辑器中的选区可以保持可见,同时编辑器 UI(例如 URL 输入字段)中也可以存在(折叠的)选区。

iframe 化的文章编辑器将通过减少样式冲突和提高布局准确性,使区块和主题开发者的工作更加轻松。

文章编辑器何时以 iframe 形式工作?

虽然包括模板编辑器在内的大多数编辑器已采用 iframe 模式,但为了向后兼容,当前文章编辑器仅在满足以下条件时才会以 iframe 形式工作(由 useShouldIframe 钩子 决定):

  • 启用 Gutenberg 插件时: 当前主题为区块主题 或 所有已注册区块的 apiVersion 均为 3 或更高版本
  • 未启用 Gutenberg 插件时: 所有已注册区块的 apiVersion 均为 3 或更高版本

简而言之,若您尚未在 iframe 编辑器中完成区块的全面测试,通过保持 apiVersion 为 2,可在多数情况下阻止文章编辑器以 iframe 形式运行。当确认您的区块能在 iframe 编辑器中正常工作后,即可迁移至 apiVersion 3。

文章编辑器何时会以 iframe 方式工作?

在 WordPress 7.0 中,无论已注册区块的 apiVersion 如何,文章编辑器都计划始终以 iframe 方式工作。

在此之前,为了鼓励开发者在 iframe 编辑器中测试,WordPress 6.9 引入了以下开发者警告和模式变更:

  • 浏览器控制台警告:当区块以 apiVersion 2 或更低版本注册时,WordPress 会在浏览器控制台中显示以下消息:
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/
  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 模式更新:block.json 模式 已更新,仅允许新创建或更新的区块使用 apiVersion: 3。旧版本(1 或 2)将不再通过模式验证。

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

所有核心区块已使用 apiVersion 3,因此只需将你的 apiVersion 更改为 3,即可让你的区块在 iframe 文章编辑器中工作。

但请确保不存在其他注册版本为 2 或更低的第三方区块。如果注册了版本 2 或更低的区块,文章编辑器可能无法作为 iframe 编辑器正常工作。

iframe 编辑器的技术考量

大多数区块无需修改即可在 iframe 编辑器中运行,但以下技术注意事项和需知事项已记录如下。

文档与窗口

iframe 将拥有与当前作为父窗口的管理页面不同的 document 和 window。编辑器脚本加载于管理页面中,因此通过访问 document 或 window 来操作内容将不再有效。

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

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

使用 useRefEffect(推荐)

如果附加事件处理程序,请注意当 ref 发生变化时,useEffect 回调不会被调用。因此,建议使用新的 useRefEffect API,该 API 在 ref 发生变化时(除了传递的任何依赖项之外)会调用给定的回调函数。

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

其他框架和库

对于编辑器,jQuery 等脚本会在父窗口(管理页面)中加载,这没有问题。当使用这些脚本与 iframe 中的区块交互时,您应该传递元素引用。

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

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

向该库提交 issue 或 PR,建议其使用 ownerDocument 和 defaultView 替代全局对象。理想情况下,任何库都应允许以 iframe 内的元素作为目标进行初始化。这并非不可能。欢迎随时联系我们提及此问题。

在此期间,你可以使用在 iframe 内加载的脚本。我们已经将所有前端脚本加载到 iframe 中以解决此类情况,但请注意,理想情况下你根本不应使用在 iframe 中加载的脚本。你可以使用 defaultView 来访问脚本。

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;

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