news 2026/9/17 8:16:21

Elementor Editor V2 编辑器包扩展实战:从 PHP 注册、UI 注入到 MCP 工具挂载

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Elementor Editor V2 编辑器包扩展实战:从 PHP 注册、UI 注入到 MCP 工具挂载

Elementor Editor V2 编辑器包扩展实战:从 PHP 注册、UI 注入到 MCP 工具挂载

【免费下载链接】elementorThe most advanced frontend drag & drop page builder. Create high-end, pixel perfect websites at record speeds. Any theme, any page, any design.项目地址: https://gitcode.com/GitHub_Trending/el/elementor

本指南围绕 Elementor 开源仓库中的 add-editor-package 示例文档,完整讲解如何通过"插件自有 Package"扩展 Editor V2 编辑器:先用 PHP 过滤器注册包名,再在 TypeScript 包的init()中注入 UI 槽位、注册面板与菜单,最后通过@elementor/editor-mcp为编辑器内 Agent(Angie / WebMCP)注册领域化 MCP 工具。读完本文,你将掌握一条可复制的端到端扩展路径,并能对照 site-navigation 模块 等仓库内真实实现进行验证。

一、前置认知:Editor V2 的包加载机制

在动手写代码之前,需要先理解"包(Package)"在 Editor V2 架构中的位置。Editor V2 是 Elementor 模块化的编辑器前端:功能以独立构建的包(React + TypeScript)为单位发布,通过 WordPress 脚本句柄(script handle)入队,并暴露在window.elementorV2全局对象上。PHP 端通过 WordPress 过滤器把包名追加进加载列表,每个被加载的包在@elementor/editor渲染之前运行一次init()完成注册。这一机制在 overview.md 中有完整描述。

从 core/editor/loader/editor-loader.php 的实现可以看到包加载的完整生命周期:

  1. Load—— 每个包经wp_enqueue_script入队,依赖来自构建产物.asset.php
  2. Env——elementor/editor/v2/scripts/env过滤器产出运行期配置,写入elementorEditorEnv全局;
  3. Init extensions—— 依次调用window.elementorV2.{packageName}?.init?.()
  4. Start app—— 调用window.elementorV2.editor.start( domElement )启动 React 外壳。

值得注意的是,init()的执行顺序遵循**脚本依赖图(dependency graph)**而非 PHP 过滤器数组的书写顺序。另外,Editor_Loader::apply_editor_filter()揭示了过滤器链的实际走向:elementor/editor/{hook}elementor/editor/v1/{hook}elementor/editor/v2/{hook}。也就是说,本文使用的elementor/editor/v2/packages是三级链路的最后一环,既兼容旧扩展,也面向新包。

二、PHP 端注册包:两个过滤器

扩展的第一步是让 WordPress 后端"认识"你的包。示例文档给出两个过滤器,一个负责把包名加入加载列表,一个负责注入运行期环境配置:

add_filter( 'elementor/editor/v2/packages', function ( array $packages ) { return array_merge( $packages, [ 'editor-my-feature' ] ); } ); add_filter( 'elementor/editor/v2/scripts/env', function ( array $env ) { $env['@elementor/editor-my-feature'] = [ 'enabled' => true ]; return $env; } );

要点拆解:

  • 第一个过滤器接收当前已注册的包名数组($packages),用array_merge追加你的包名'editor-my-feature'。包名是字符串,最终会映射到window.elementorV2.editorMyFeature(camelCase 化)并触发其init()
  • 第二个过滤器以@elementor/{包名}为键写入任意 JSON 可序列化的配置,前端包可通过@elementor/env等机制读取。示例中的enabled只是示范,实际可放入任何业务数据。

仓库内最直接的"标准答案"是 modules/site-navigation/module.php:

const PACKAGES = [ 'editor-site-navigation', ]; // 构造函数中: add_filter( 'elementor/editor/v2/packages', fn( $packages ) => $this->add_packages( $packages ) ); add_filter( 'elementor/editor/v2/scripts/env', function( $env ) { $env['@elementor/editor-site-navigation'] = [ 'is_pages_panel_active' => Plugin::$instance->experiments->is_feature_active( self::PAGES_PANEL_EXPERIMENT_NAME ), ]; return $env; } );

可以看到生产代码的两种进阶做法:其一,用Plugin::$instance->experiments->is_feature_active()把实验开关(Experiment)状态注入 env,前端据此决定是否渲染面板;其二,包名收敛为模块常量PACKAGES,由array_merge( $packages, self::PACKAGES )统一合并,便于集中管理。文档 extending-editor.md 还展示了一种"按条件返回"的写法——功能未激活时直接返回原数组、不注册包,实现延迟/按需加载。

三、JS 端创建包:init()契约与入口导出

包的核心是src/init.ts中的init()函数。构建体系(webpack 构建尾部)会自动调用window.elementorV2.{camelCasePackage}?.init?.()——例如包名editor-my-feature对应window.elementorV2.editorMyFeatureinit()同步注册函数:只做注册,不负责渲染,渲染工作交给注入到各槽位的 React 组件完成。

init()契约在 overview.md 中定义为:

window.elementorV2.{packageName}?.init?.();

示例文档给出的init.ts结构:

import { injectIntoPageIndication, toolsMenu } from '@elementor/editor-app-bar'; import { getMCPByDomain } from '@elementor/editor-mcp'; import { z } from '@elementor/schema'; import { MyIndicator } from './components/my-indicator'; import { useMyToggleProps } from './hooks/use-my-toggle-props'; export function init() { injectIntoPageIndication( { id: 'my-indicator', component: MyIndicator, } ); toolsMenu.registerToggleAction( { id: 'toggle-my-panel', priority: 20, useProps: useMyToggleProps, } ); const mcp = getMCPByDomain( 'my_feature', { instructions: 'Short hint for agents', docs: 'Full domain documentation', } ); mcp.addTool( { name: 'my_tool', description: 'Does something in the editor', schema: { elementId: z.string().describe( 'Target element id' ), }, handler: async ( { elementId } ) => `Handled ${ elementId }`, } ); }

同时必须从src/index.ts再导出init,构建脚本才能识别包入口:

// src/index.ts export { init } from './init';

仓库内最贴近此示例的真实实现是 packages/packages/core/editor-site-navigation/src/init.ts:它同样调用injectIntoPageIndication()注册顶部栏组件,通过toolsMenu.registerToggleAction()注册带priority: 6的开关动作,并在实验开关激活时用registerPanel( panel )注册滑入面板——idcomponentpriorityuseProps等参数与示例一一对应,是阅读 API 用法的绝佳范本。

四、注入 API 全景:六大扩展区域

示例文档用一张表概括了常用注入 API,按"编辑器区域 → 注入 API → 所属包"组织:

区域API所属包
Shell 外壳injectIntoTopinjectIntoLogic@elementor/editor
顶栏 App barinjectIntoPageIndicationtoolsMenu.registerToggleAction@elementor/editor-app-bar
样式选项卡injectIntoStyleTab@elementor/editor-editing-panel
元素面板injectTab@elementor/editor-elements-panel
样式仓库stylesRepository.register@elementor/editor-styles-repository
v1 桥接registerDataHookblockCommand__privateListenTo@elementor/editor-v1-adapters

对照 extending-editor.md 的完整 Public API 表,还可补充几个同族入口:

  • 编辑面板替换:registerEditingPanelReplacement@elementor/editor-editing-panel),按元素类型条件替换整个面板;
  • 站点设置选项卡:injectSiteSettingsTab@elementor/editor-site-settings);
  • 滑入面板:registerPanel@elementor/editor-panels);
  • Redux slice:__registerSlice@elementor/store);
  • MCP 领域:getMCPByDomain@elementor/editor-mcp)。

从 packages/packages/core/editor/src/index.ts 的导出可以看到 Shell 层的两个注入点injectIntoTopinjectIntoLogicstart()同源发布,这印证了"外壳槽位由@elementor/editor提供"的设计。需要做"旧事件桥接"的场景(React UI 里监听 v1 编辑器事件),则依赖@elementor/editor-v1-adaptersregisterDataHookblockCommand,以及listenTo( v1ReadyEvent(), fn )——注意带__private前缀的导出属于内部 API,不建议在生产代码中使用。

五、MCP 命名空间与工具注册

Editor V2 的包扩展不仅是 UI 注入,还包含编辑器内 MCP(Model Context Protocol)层:包可在init()中调用getMCPByDomain()暴露能力给 Angie 与 WebMCP。这与 PHP 端modules/mcp/的"能力(abilities)"是两套独立体系——本文示例文档特别标注了这一点:这里注册的是插件自有包内的 in-editor MCP 工具,不涉及 PHP MCP abilities

两条硬性规则必须遵守:

  1. 命名空间格式:必须是/^[a-z_]+$/,即仅小写字母加下划线。示例中的'my_feature'符合规范,my-feature之类的连字符命名不被接受;
  2. Schema 类型addToolschema字段是来自@elementor/schemaZod 对象,而非纯 JSON Schema。运行时由 registry 将 Zod raw shape 转换为 JSON Schema 交给宿主。

getMCPByDomain的完整语义可在 registering-editor-tools.md 中找到:它返回/创建名为editor-{namespace}的领域服务器(domain server),可传入instructions(给 Agent 的简短提示)与docs(领域完整文档)。其中options.docs会自动注册elementor://{namespace}/server-docs资源,并合并进工具的requiredResources——这正是示例中docs: 'Full domain documentation'一行背后的机制。

addTool的字段语义(同样引自上述文档):

字段用途
name暴露给宿主的工具名
description面向 Agent 的工具描述
schemaZod raw shape,运行期转 JSON Schema
outputSchema可选;启用后自动附加errors字段
isDestructive映射到destructiveHint,标记破坏性操作
requiredResources{ uri, description }[],会前置拼接到工具描述中
handler异步(args) => result;抛错时宿主侧收到isError: true

六、验证清单:如何确认扩展生效

示例文档以三条可观测结果收尾,构成最小验证闭环:

  1. 包出现在编辑器网络请求中:打开浏览器开发者工具,过滤js/packages/,应能看到你的包脚本被加载。注意 editor-loader.php 的命名规律是{assets_url}js/packages/{package}/{package}{min_suffix}.js,例如js/packages/editor-my-feature/editor-my-feature.min.js
  2. UI 渲染在指定槽位:注入的组件应出现在所选区域——顶栏指示器出现在页面指示区,切换按钮出现在工具菜单中(可观察priority对排序的影响);
  3. MCP 工具可见:当 Angie / WebMCP 实验启用后,my_tool应出现在 Agent 可用工具列表,且elementId参数按 Zod schema 校验。

七、源码级进阶:从示例到生产实践的差异

将示例与仓库真实代码对照,还能提炼出三条生产级实践:

1. 用实验开关控制加载。site-navigation 模块在 env 中注入is_pages_panel_active供前端判断,同时仅当实验激活时才注册 REST 字段。示例中enabled: true的写法可升级为Plugin::$instance->experiments->is_feature_active( ... )驱动的真实开关。

2. 创建新包有完整流程。如果功能足够独立、需要新建包而非复用现有包,参考 packages/docs/creating-a-new-package.md:新建目录与package.json→ 编写源码 → 在 Demo 应用测试 → 在 Elementor 插件内联调 → 补单元测试 → 提 PR。加入插件的方式有两种:直接写死在core/editor/loader/editor-loader.php的包列表(始终加载),或走过滤器(条件加载、便于模块化封装)。

3. 基础库与扩展包分离。Editor_Loader::LIBS(见 editor-loader.php)维护了locationsstoreschemaeditor-v1-adapters等基础库,EXTENSIONS维护默认扩展;你的包通过elementor/editor/v2/packages追加后,与基础库一起入队、按依赖图排序初始化。

八、延伸阅读

  • 编辑器包扩展完整指南——本示例的上游详版文档,含init()契约、@elementor/locations位置表与更多代码示例;
  • 编辑器包总览——包分类、生命周期与加载器内部实现;
  • 注册编辑器内 MCP 工具——getMCPByDomainaddTool字段、适配器模式与命名空间规则的权威出处;
  • Site Navigation 模块真实实现——PHP 注册的最佳实践范本;
  • Editor Site Navigation 包 init 实现——init()中组合injectIntoPageIndicationtoolsMenu.registerToggleActionregisterPanel的完整范例。

【免费下载链接】elementorThe most advanced frontend drag & drop page builder. Create high-end, pixel perfect websites at record speeds. Any theme, any page, any design.项目地址: https://gitcode.com/GitHub_Trending/el/elementor

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 8:16:17

FastJSON2替代Jackson的Spring Boot JSON处理方案

1. 为什么选择FastJSON2替代JacksonSpring Boot默认集成Jackson作为JSON处理器,但在某些场景下FastJSON2可能更具优势。FastJSON2是阿里巴巴开源的JSON处理库,相比Jackson有以下特点:性能优势:FastJSON2在序列化/反序列化速度上比…

作者头像 李华
网站建设 2026/9/17 8:15:37

Ubuntu企业级部署与云服务优化实践

1. 项目背景与核心价值作为Linux发行版中的明星产品,Ubuntu系统及其生态服务在开发者群体和企业环境中占据着重要地位。最近我在梳理公司内部技术栈时,系统整理了Ubuntu平台的全套产品线和服务体系,发现很多功能模块之间存在有趣的协同效应。…

作者头像 李华
网站建设 2026/9/17 8:14:45

MybatisX插件完全指南:安装配置、双向跳转与CRUD代码生成

1. 从“文档跳一年”到“一键搞定”:为什么要装MybatisXMybatisX这东西,严格来说不是框架,也不是工具库,它是IDEA里的一个插件,官方出品,专门伺候MyBatis和MyBatis-Plus的用户。我最早是在一次代码review的…

作者头像 李华
网站建设 2026/9/17 8:13:17

电力系统优化调度算法:MILP与启发式方法实践

1. 电力系统优化调度算法概述电力系统优化调度是电力行业的核心技术难题,它直接关系到电网运行的经济性、安全性和环保性。作为一名在电力行业摸爬滚打多年的工程师,我深知一套优秀的优化算法对电网调度意味着什么——它可能意味着每年节省数千万的运行成…

作者头像 李华
网站建设 2026/9/17 8:12:04

Jetson Orin GPU零拷贝通信方案解析:打破机器人感知链路瓶颈

做机器人这套系统的朋友这几年应该都有一個体感:算力越来越猛,数据越来越多,但中间那层通信却经常成为整个链路的瓶颈。Jetson Orin 平台上跑感知模型,GPU 推理本身只要十几毫秒,结果数据从显存拷到内存、再从内存拷到…

作者头像 李华
网站建设 2026/9/17 8:09:26

深度评测DeskcommCRM:桌面端客户管理系统如何打通沟通与跟进全流程

1. 为什么我最后选定了DeskcommCRM这套桌面沟通型客户管理系统1.1 一个让销售团队抓狂的真实场景先说背景。去年我带的小团队大概十几个销售,每天要同时处理电话、企业微信、邮件、官网表单四五个渠道的客户咨询。最崩溃的时候,一个客户上午在官网留了言…

作者头像 李华