Elementor@elementor/wp-media包完全指南:WordPress 媒体库适配器的能力演进与源码剖析
【免费下载链接】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 前端拖拽页面构建器仓库中的@elementor/wp-media包(路径 packages/packages/libs/wp-media)为对象,从它的 CHANGELOG.md 版本演进出发,结合源码与测试,系统讲解该包如何封装 WordPress 的wp.media函数、打开媒体弹窗、选择/上传图片、读取附件数据,以及支持 SVG 上传、限制图片类型、URL 导入等能力的实现原理。读完本文,你将掌握这个包从 0.1.0 到 0.6.1 每个版本能力的用法、底层调用链与约束条件,并能在自己的 Elementor 编辑器扩展中正确接入它。
一、包定位:WordPresswp.media的前端适配层
@elementor/wp-media在仓库中位于packages/packages/libs/wp-media,其 README.md 明确指出:
This package is an adapter for WordPress'
wp.mediafunction. It allows you to open the WordPress media modal, select or upload images from the media library, and get attachment data.
它假设wp.media存在于全局作用域,否则会抛出异常。因此使用方必须把media-modelshandle 加入自身脚本的 dependencies 数组,确保在 WordPress 后台环境中加载了媒体库模型脚本。
从 package.json 可以看到该包的发布形态:
- 名称
@elementor/wp-media,描述 "An adapter for WordPress' media utils"; - 通过
tsup构建,同时输出dist/index.js(CommonJS)、dist/index.mjs(ESM)与dist/index.d.ts(类型声明),exports字段按import/require分别指向对应产物; - 依赖
@elementor/query(4.4.0)与@elementor/utils(4.4.0),peerDependency 为react@^18.3.1; - 发布包仅包含
README.md、CHANGELOG.md、/dist与/src,测试目录(__tests__)不随包发布。
包的公开 API 极简,见 src/index.ts:
export type { Attachment } from './types/attachment'; export type { OpenOptions, MediaType } from './hooks/use-wp-media-frame'; export { default as useWpMediaAttachment } from './hooks/use-wp-media-attachment'; export { default as useWpMediaFrame } from './hooks/use-wp-media-frame'; export { getMediaAttachment } from './get-media-attachment';对外总共暴露:两个 React Hook(useWpMediaFrame、useWpMediaAttachment)、一个命令式函数(getMediaAttachment)以及若干类型。下面结合 CHANGELOG 的版本脉络逐一深入。
二、CHANGELOG 版本演进图谱:0.1.0 → 0.6.1
CHANGELOG.md 记录了该包从诞生到当前(包内 package.json 版本为 4.4.0,CHANGELOG 则记录早期独立发版历程)的全部功能变更。将各版本的核心变更整理如下:
| 版本 | 类型 | 核心变更 | 对应能力 |
|---|---|---|---|
| 0.1.0 (2024-08-14) | Feature | 新增与 wp-media 交互的 utils(EDS-324) | 包首次发布,提供媒体交互基础能力 |
| 0.1.1 (2024-08-14) | Bug Fix | 关闭时清理(cleanup on close,EDS-365) | 修复媒体弹窗关闭后的资源泄漏 |
| 0.1.2 (2024-08-20) | — | 仅版本号升级 | 无功能变更 |
| 0.2.0 | Minor | ImageControl支持 inner props | 内部控件能力扩展 |
| 0.2.1 | Patch | 修复 package.json 的exports字段,升级@elementor/query与@elementor/utils | 包导出解析修正 |
| 0.2.2 | Patch | 升级@elementor/utils@0.3.0 | 依赖同步 |
| 0.2.3 | Patch | 更新并锁定依赖版本 | 依赖固化 |
| 0.3.0 | Minor | 支持 SVG 上传(4e5ea74) | MediaType引入'svg' |
| 0.4.0 | Minor | 将getMediaAttachment从 React hook 中分离(af5fa42);支持限制上传图片类型(a2f5096) | 命令式 API 与类型过滤 |
| 0.4.1 | Patch | 升级@elementor/utils@0.3.1 | 依赖同步 |
| 0.4.2 | Patch | 升级@elementor/utils@0.4.0 | 依赖同步 |
| 0.5.0 | Minor | 新增 API client 与 hooks,用于启用 unfiltered files 上传(f6a4d4f) | 非过滤文件上传能力 |
| 0.6.0 | Minor | 新增 settings transformers(b8a7725) | 设置转换能力 |
| 0.6.1 | Patch | 升级@elementor/utils@0.5.0 | 依赖同步 |
值得注意的是,后续版本号虽然继续增长,但包内 version 已并入 Elementor 主仓库的 monorepo 版本(当前为 4.4.0),CHANGELOG 中的 0.x 记录是该包独立发版时期的历史。这张表既是包的"能力时间线",也是本文随后逐项深入的主线。
三、核心一:useWpMediaFrame——打开并控制媒体弹窗
useWpMediaFrame是包的主入口,定义于 src/hooks/use-wp-media-frame.ts。它接收一个配置对象,返回{ open },由调用方在用户交互时触发弹窗。
3.1 配置项完整说明
Hook 的Options类型如下(带注释为源码未标注、由实现推断的默认行为):
type Options = { mediaTypes: MediaType[]; // 允许的媒体类型:'image' | 'svg' | 'video' title?: string; // 弹窗标题 allowUrlImport?: boolean; // 是否允许 URL 导入(开启后使用 post 型 frame) onSelectUrl?: ( url: string, alt?: string ) => void; // URL 导入回调 } & ( | { multiple: true; selected: Array< number | null >; // 多选时预选中附件 id 数组 onSelect: ( val: Attachment[] ) => void; // 多选回调,返回附件数组 } | { multiple: false; selected: number | null; // 单选时预选中附件 id onSelect: ( val: Attachment ) => void; // 单选回调,返回单个附件 } );而每次调用open( openOptions )时还可传入OpenOptions,覆盖打开时的行为:
export type OpenOptions = { mode?: 'upload' | 'browse' | 'url'; // 默认 'browse' currentUrl?: string; // mode 为 'url' 时预填的 URL currentAlt?: string; // mode 为 'url' 时预填的 alt 文本 };3.2 弹窗创建与生命周期
createFrame是核心工厂函数,它做了以下几件事:
- 调用
media()( { title, multiple, library } )创建 frame:library.type由getMimeTypes( mediaTypes )根据媒体类型映射为 MIME 数组(见下文 3.4)。 allowUrlImport时改用frame: 'post':WordPress 的 post frame 自带"从 URL 插入"面板。- 监听
open事件:设置上传参数uploadTypeCaller(标记为elementor-wp-media-upload,便于服务端识别来源)、应用 mode、并预选已选附件。 - 监听
insert select事件:统一走select函数分发结果。 - 处理扩展名白名单:见 3.3。
生命周期清理策略非常明确:不在 close 时销毁 frame,而是在下次open()或组件卸载时统一清理。cleanupFrame依次调用frame.detach()与frame.remove()。这一设计避免了与 WordPress 内部媒体生命周期发生竞态(race condition),对应 CHANGELOG 0.1.1 的 "cleanup on close [EDS-365]" 修复——它把清理时机从 close 推迟到了更安全的节点。
3.3 上传扩展名的临时接管与还原
handleExtensions利用全局_wpPluploadSettings实现"打开期间限制可上传类型、关闭后恢复默认":
ready时:把_wpPluploadSettings.defaults.filters.mime_types替换为按mediaTypes计算出的扩展名列表;close时:恢复打开前保存的默认扩展名列表。
这正是 CHANGELOG 0.4.0 中 "support restricting uploaded image type (a2f5096)" 的实现。读取全局设置的行为封装在 src/wp-plupload-settings.ts:若window._wpPluploadSettings不存在(即从未打开过 WP 上传器),会抛出WpPluploadSettingsNotAvailableError,错误提示明确要求"先确保一个 wp media uploader 处于打开状态"。
3.4 媒体类型 → MIME / 扩展名映射
源码中维护了两张静态映射表:
图片(image):avif, bmp, gif, ico, jpe, jpeg, jpg, png, webp,MIME 为对应的image/*。SVG(svg):扩展名svg,MIME 为image/svg+xml(CHANGELOG 0.3.0 新增)。视频(video):扩展名mp4, webm, ogg, mov, m4v, avi, wmv, mpg, mpeg, 3gp, 3g2,MIME 为video/mp4, video/webm, video/ogg, video/quicktime, video/x-m4v, video/avi, video/x-ms-wmv, video/mpeg, video/3gpp, video/3gpp2。
getMimeTypes用于限定媒体库浏览范围(library.type),getExtensions用于生成上传时的扩展名白名单。二者都由mediaTypes.reduce(...)累加生成,因此传入['image', 'svg']即可同时允许位图与 SVG。
3.5 三种 mode 的行为差异
- browse(默认):
frame.content.mode('browse'),展示媒体库浏览界面; - upload:
frame.content.mode('upload'),直接展示上传标签页; - url:
frame.setState('embed')切换到 embed 状态,并(若传入currentUrl/currentAlt)通过setTimeout(..., 0)延迟写入url/alt属性。延迟原因在源码注释中有明确说明:需要等 toolbar 区域初始化完成后再触发change:url的刷新回调。
select分发时若state.get('id') === 'embed',则走onSelectUrl(url, alt)分支(仅当 URL 非空);否则把 selection 序列化后经normalize转换,按multiple决定回传数组还是单个附件。
3.6 测试印证
src/hooks/tests/use-wp-media-frame.test.ts 用 mock frame 覆盖了关键行为:
- 打开时传入
title、multiple、mediaTypes: ['image'],断言library.type恰好等于 9 个图片 MIME 的数组; - 单选触发
select/insert事件各回调一次,多选回传附件数组; mode: 'upload'时 frame 的 content mode 为upload;- 再次
open()会先detach+remove旧 frame;组件卸载同样清理; - 仅触发
close不会触发清理(避免 WP 生命周期竞态),验证了 0.1.1 的修复策略。
四、核心二:getMediaAttachment与useWpMediaAttachment——按 ID 读取附件
CHANGELOG 0.4.0 的 "SeparategetMediaAttachmentfrom react hook" 正是把附件读取逻辑从 hook 中抽离为可独立使用的命令式 API。
4.1fetchAttachmentFromWP的读取策略
src/get-media-attachment.ts 中的核心流程:
export async function fetchAttachmentFromWP( id: number ) { const model = media().attachment( id ); const wpAttachment = model.toJSON(); const isFetched = 'url' in wpAttachment; // 已加载过的模型带 url 字段 if ( isFetched ) { return normalize( wpAttachment ); // 命中模型内存缓存,直接返回 } try { return normalize( await model.fetch() ); // 否则走 REST 拉取 } catch { return null; // 失败返回 null 而非抛错 } }关键判断是"url是否已存在于模型 JSON 中":WordPress 的 Backbone attachment 模型一旦被 fetch 过就会带url字段,据此判断缓存命中,避免重复请求。
4.2 与@elementor/query的缓存集成
getMediaAttachment通过getQueryClient()获取全局 QueryClient,用ensureQueryData以['wp-attachment', id]作为 queryKey 写入缓存。这样:
- 同一附件 id 的并发请求会被去重(get-media-attachment.test.ts 中
should deduplicate concurrent fetches用例验证attachment只被调用一次); - 二次读取直接命中缓存,不再触碰
wp.media(should return cached attachment without fetching wp.media again用例验证); id为null时直接返回null,不上发请求。
4.3 Hook 封装
src/hooks/use-wp-media-attachment.ts 只是useQuery的一层薄封装:
export default function useWpMediaAttachment( id: number | null ) { return useQuery( { queryKey: [ 'wp-attachment', id ], queryFn: () => fetchAttachmentFromWP( id as number ), enabled: !! id, // id 为空时不发起请求 } ); }返回值即@elementor/query的标准查询结果(data、isLoading、isError等),可直接用于 React 组件渲染。
五、核心三:normalize与Attachment类型——统一附件数据结构
wp.media返回的附件 JSON 字段与包内定义的Attachment类型并不一致,src/normalize.ts 负责转换:
export default function normalize( attachment: WpAttachmentJSON ): Attachment { const { filesizeInBytes, filesizeHumanReadable, author, authorName, ...rest } = attachment; return { ...rest, filesize: { inBytes: filesizeInBytes, // 扁平字段 → 嵌套对象 humanReadable: filesizeHumanReadable, }, author: { id: parseInt( author ), // 字符串 id → number name: authorName, }, }; }归一化后的 Attachment 类型包含:id、url、height、width、alt、filename、title、mime、type、subtype、uploadedTo,以及嵌套的filesize: { inBytes, humanReadable }、author: { id, name }和sizes: Record<string, { width, height, url }>。所有选中附件与getMediaAttachment的返回值都经过该函数,保证下游消费的是统一、可预测的结构。
六、错误模型:两个全局依赖的前置校验
包对 WordPress 全局对象的依赖通过两个工厂函数显式校验:
- src/media.ts:检查
window.wp?.media是否存在,否则抛出WpMediaNotAvailableError(codewp_media_not_available),错误消息明确指出需要在 dependencies 数组中包含media-modelshandle。 - src/wp-plupload-settings.ts:检查
window._wpPluploadSettings是否存在,否则抛出WpPluploadSettingsNotAvailableError(codewp_plupload_settings_not_available)。
两个错误均通过@elementor/utils的createError工厂创建(见 src/errors.ts),携带稳定错误码,便于上层统一捕获与上报。
src/tests/media.test.ts 验证了三种场景:window.wp不存在时抛错、window.wp.media不存在时抛错、存在时原样返回wp.media。这提醒接入方:该包只能在 WordPress 后台脚本环境中使用,前端公共页面没有wp.media全局对象。
七、实战示例:在 Elementor 编辑器扩展中接入媒体选择
综合以上 API,一个典型的"选择一张图片(可选 URL 导入)"用法如下:
import { useWpMediaFrame, type Attachment } from '@elementor/wp-media'; function ImagePicker( { value, onChange }: { value: number | null; onChange: ( attachment: Attachment ) => void; } ) { const { open } = useWpMediaFrame( { mediaTypes: [ 'image', 'svg' ], // 允许位图与 SVG(依赖 0.3.0+) title: 'Select an image', multiple: false, selected: value, // 预选中当前值 allowUrlImport: true, // 允许粘贴图片 URL onSelect: ( attachment ) => onChange( attachment ), onSelectUrl: ( url, alt ) => onChange( { /* 以 url 构造的附件 */ } ), } ); return <button onClick={ () => open( { mode: 'browse' } ) }>Select Image</button>; }接入时务必满足两个前置条件:
- 在你的脚本注册中把
media-models加入 dependencies(否则WpMediaNotAvailableError会被抛出); - 包依赖 React 18 与
@elementor/query的 QueryClient 环境(getMediaAttachment依赖全局 query client 做缓存)。
若要限制用户只能上传而不能浏览已有库,可将open( { mode: 'upload' } );若要允许直接粘贴外链图片,则使用mode: 'url'并配合currentUrl/currentAlt预填。
八、小结:从 CHANGELOG 反推包的能力边界
把 CHANGELOG 与源码对照,可以清晰还原@elementor/wp-media的能力边界:
- 它做什么:封装
wp.media弹窗(浏览/上传/URL 三种模式)、按类型(image/svg/video)过滤媒体库与上传扩展名、返回归一化的Attachment结构、按 ID 读取附件并做查询缓存,同时提供 React hook 与命令式两种调用形态。 - 它不做什么:不做服务端上传鉴权(
uploadTypeCaller只是给服务端一个标识)、不渲染任何 UI(弹窗完全由 WordPress 原生媒体库提供)、不脱离 WordPress 后台运行(强依赖wp.media与_wpPluploadSettings两个全局对象)。
对于在 Elementor 生态内开发编辑器扩展、需要与 WordPress 媒体库深度集成的开发者而言,这个包把繁琐的 Backbone frame 生命周期、MIME 过滤、数据归一化全部封装完毕,通过两个 hook 与一个函数即可获得完整、可测试、带缓存能力的媒体能力层。
进一步阅读:包 README、CHANGELOG、frame hook 测试、附件读取测试。
【免费下载链接】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),仅供参考