关于 wix-cli-site-plugin
wix-cli-site-plugin 是用于 Wix CLI 应用的开发技能,指导创建可放入 Wix 商业解决方案预设插槽的自定义元素插件。内容涵盖组件、编辑器设置面板与扩展注册,适合开发 Wix 网站扩展的开发者。
主要功能
- 01自定义元素插件结构
- 02Wix Editor 设置面板
- 03插槽放置与扩展注册
- 04属性与 HTML 特性绑定
- 05颜色和字体选择器
适用场景
- 01在商品页添加可配置徽章
- 02为预约页显示确认信息
- 03展示商品评价摘要
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wix/ skills --skill wix-cli-site-plugin安装后在 Claude Code 中输入/wix-cli-site-plugin确认已加载。
附带文件references/EXAMPLES.mdreferences/SLOTS.md
详细介绍
Wix 站点插件构建器
为 Wix CLI 应用创建站点插件扩展。站点插件是自定义元素,可集成到 Wix 商业解决方案(如 Wix Stores、Wix Bookings、Wix eCommerce)中预定义的 插槽,以扩展其功能和用户体验。
站点所有者可以使用 Wix 编辑器中的插件浏览器,将站点插件放入 UI 插槽。
快速入门清单
创建站点插件时,请按顺序完成以下步骤:
- 创建插件文件夹:
src/extensions/site/plugins/<plugin-name>/ - 创建一个继承
HTMLElement并带有observedAttributes的<plugin-name>.tsx - 创建包含 WDS 组件以及
widget.getProp/setProp的<plugin-name>.panel.tsx - 创建包含
extensions.sitePlugin()和唯一 UUID 的<plugin-name>.extension.ts - 更新
src/extensions.ts,导入并使用新扩展
架构
站点插件由 三个必需文件组成:
1. 插件组件(<plugin-name>.tsx)
使用原生 HTMLElement 在插槽中渲染的自定义元素组件:
- 继承
HTMLElement类 - 定义
observedAttributes,实现响应式属性 - 实现
connectedCallback()和attributeChangedCallback()以进行渲染 - 使用模板字符串设置内联样式
- 属性使用 kebab-case(例如,
display-name)
2. 设置面板(<plugin-name>.panel.tsx)
显示在 Wix 编辑器侧边栏中的设置面板:
- 使用 Wix Design System(
@wix/design-system)组件——组件参考请参阅 wds-docs - 通过
@wix/editorwidget API 管理插件属性 - 使用
widget.getProp('kebab-case-name')加载初始值 - 使用
widget.setProp('kebab-case-name', value)更新属性 - Widget 属性会绑定到自定义元素属性——任何属性更改都会自动更新相应的属性
- 内容需封装在
WixDesignSystemProvider > SidePanel > SidePanel.Content中
3. 扩展配置(<plugin-name>.extension.ts)
定义插件的放置配置:
- 指定插件可以添加到哪些插槽
- 配置应用安装时的自动添加行为
- 设置标签名称和文件路径
插件组件模式
站点插件使用原生 HTMLElement 自定义元素:
// my-site-plugin.tsx
class MyElement extends HTMLElement {
static get observedAttributes() {
return ['display-name'];
}
constructor() {
super();
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
render() {
const displayName = this.getAttribute('display-name') || "Your Plugin's Title";
this.innerHTML = `
<div style="font-size: 16px; padding: 16px; border: 1px solid #ccc; border-radius: 8px; margin: 16px;">
<h2>${displayName}</h2>
<hr />
<p>
This is a Site Plugin generated by Wix CLI.<br />
Edit your element's code to change this text.
</p>
</div>
`;
}
}
export default MyElement;
要点:
- 直接继承
HTMLElement类 - 定义
observedAttributes静态 getter,列出响应式属性 - 属性使用 kebab-case(例如,
display-name、bg-color) - 实现
connectedCallback()进行初始渲染 - 实现
attributeChangedCallback(),在属性更改时重新渲染 - 使用模板字符串设置内联样式
- 使用
this.getAttribute('attribute-name')读取属性值 - Wix 会为你处理
define()——请勿在代码中调用customElements.define()
设置面板模式
// my-site-plugin.panel.tsx
import React, { type FC, useState, useEffect, useCallback } from 'react';
import { widget } from '@wix/editor';
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
} from '@wix/design-system';
import '@wix/design-system/styles.global.css';
const Panel: FC = () => {
const [displayName, setDisplayName] = useState<string>('');
useEffect(() => {
widget.getProp('display-name')
.then(displayName => setDisplayName(displayName || "Your Plugin's Title"))
.catch(error => console.error('Failed to fetch display-name:', error));
}, [setDisplayName]);
const handleDisplayNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newDisplayName = event.target.value;
setDisplayName(newDisplayName);
widget.setProp('display-name', newDisplayName);
}, [setDisplayName]);
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Content noPadding stretchVertically>
<SidePanel.Field>
<FormField label="Display Name">
<Input
type="text"
value={displayName}
onChange={handleDisplayNameChange}
aria-label="Display Name"
/>
</FormField>
</SidePanel.Field>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;
要点:
widget.getProp()和widget.setProp()中的属性名称使用 kebab-case(例如,"display-name")- 在
onChange处理程序中,始终同时更新本地状态和 widget 属性 - Widget 属性会绑定到自定义元素属性——更改会自动更新相应的属性
- 将内容封装在
WixDesignSystemProvider > SidePanel > SidePanel.Content中 - 使用
@wix/design-system中的 WDS 组件 - 导入
@wix/design-system/styles.global.css以加载样式 - 添加
aria-label以确保无障碍访问
颜色和字体选择器字段
站点插件设置面板可以使用 @wix/editor 中的 inputs.selectColor() 和 inputs.selectFont(),打开 Wix 编辑器原生的颜色和字体选择器对话框。
ColorPickerField
打开包含主题颜色、渐变等选项的 Wix 颜色选择器——不是基础 HTML <input type="color">。
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
interface ColorPickerFieldProps {
label: string;
value: string;
onChange: (value: string) => void;
}
export const ColorPickerField: FC<ColorPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Box width="30px" height="30px">
<FillPreview
fill={value}
onClick={() => inputs.selectColor(value, { onChange: (val) => { if (val) onChange(val); } })}
/>
</Box>
</FormField>
</SidePanel.Field>
);
FontPickerField
打开 Wix 字体选择器,其中包含字体系列、字号、粗体、斜体和其他排版功能。
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Button, Text, SidePanel } from '@wix/design-system';
interface FontValue {
font: string;
textDecoration: string;
}
interface FontPickerFieldProps {
label: string;
value: FontValue;
onChange: (value: FontValue) => void;
}
export const FontPickerField: FC<FontPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Button
size="small"
priority="secondary"
onClick={() => inputs.selectFont(value, { onChange: (val) => onChange({ font: val.font, textDecoration: val.textDecoration || "" }) })}
fullWidth
>
<Text size="small" ellipsis>Change Font</Text>
</Button>
</FormField>
</SidePanel.Field>
);
重要:
- 始终将
@wix/editor中的inputs.selectColor()与FillPreview搭配使用 — 不要使用<Input type="color"> - 始终将
@wix/editor中的inputs.selectFont()与回调模式inputs.selectFont(value, { onChange })搭配使用 - 从
@wix/editor导入inputs(不要从@wix/sdk导入)
属性命名约定
站点插件的 HTML 属性统一使用 kebab-case:
| 文件 | 约定 | 示例 |
|---|---|---|
<plugin>.tsx (getAttribute) | kebab-case | this.getAttribute('display-name') |
<plugin>.tsx (observedAttributes) | kebab-case | ['display-name', 'bg-color'] |
<plugin>.panel.tsx (widget API) | kebab-case | widget.getProp('display-name') |
输出结构
站点插件位于 src/extensions/site/plugins 下。每个插件都有自己的文件夹,文件以插件名称命名。
src/extensions/site/plugins/
└── {plugin-name}/
├── {plugin-name}.tsx # Main plugin component (HTMLElement)
├── {plugin-name}.panel.tsx # Settings panel component
└── {plugin-name}.extension.ts # Extension registration
public/
└── {plugin-name}-logo.svg # Plugin logo (optional)
参考
| 主题 | 参考 |
|---|---|
| 完整示例 | EXAMPLES.md |
| 插槽(应用 ID、多个放置位置、查找插槽) | SLOTS.md |
| WDS 组件 | wds-docs |
可用插槽
站点插件会集成到 Wix 商业解决方案中的预定义插槽。每个插槽由以下内容标识:
- appDefinitionId:Wix 应用的 ID(例如 Stores、Bookings)
- widgetId:包含该插槽的页面的 ID
- slotId:特定插槽的标识符
常见的放置区域包括商品页面(Wix Stores)、结账和侧边购物车(Wix eCommerce)、预约页面(Wix Bookings)、服务页面、活动页面和博客文章页面。
有关受支持的页面、常见 Wix 应用 ID 以及如何查找插槽 ID,请参阅 SLOTS.md。
扩展注册
扩展注册是必需的,且包含两个必要步骤。
步骤 1:创建插件专属扩展文件
每个网站插件都需要在其文件夹中创建一个扩展文件:
// my-site-plugin.extension.ts
import { extensions } from '@wix/astro/builders';
export default extensions.sitePlugin({
id: '{{GENERATE_UUID}}',
name: 'My Site Plugin',
marketData: {
name: 'My Site Plugin',
description: 'Marketing Description',
logoUrl: '{{BASE_URL}}/my-site-plugin-logo.svg',
},
placements: [{
appDefinitionId: 'a0c68605-c2e7-4c8d-9ea1-767f9770e087',
widgetId: '6a25b678-53ec-4b37-a190-65fcd1ca1a63',
slotId: 'product-page-details-6',
}],
installation: { autoAdd: true },
tagName: 'my-site-plugin',
element: './extensions/site/plugins/my-site-plugin/my-site-plugin.tsx',
settings: './extensions/site/plugins/my-site-plugin/my-site-plugin.panel.tsx',
});
重要:生成 UUID
id 必须是唯一且固定的 UUID v4 字符串。每个扩展都要生成一个全新的 UUID——不要使用 randomUUID(),也不要复制示例中的 UUID。将 {{GENERATE_UUID}} 替换为新生成的 UUID,例如 "95a28afd-7df1-4e09-9ec1-ce710b0389a0"。
| 属性 | 类型 | 说明 |
|---|---|---|
id | 字符串 | 唯一且固定的 UUID v4(请生成新的 UUID) |
name | 字符串 | 插件的内部名称 |
marketData.name | 字符串 | 插件浏览器和应用控制面板中显示的名称 |
marketData.description | 字符串 | 插件浏览器和应用控制面板中显示的描述 |
marketData.logoUrl | 字符串 | Logo 文件路径({{BASE_URL}} 会解析为公共文件夹) |
placements | 数组 | 可添加插件的插槽位置数组 |
placements.appDefinitionId | 字符串 | 包含该插槽的 Wix 应用的 ID |
placements.widgetId | 字符串 | 包含该插槽的页面的 ID |
placements.slotId | 字符串 | 特定插槽的 ID |
installation.autoAdd | 布尔值 | 应用安装时是否自动将插件添加到插槽中 |
tagName | 字符串 | HTML 自定义元素标签(kebab-case,必须包含连字符) |
element | 字符串 | 插件组件的相对路径 |
settings | 字符串 | 设置面板组件的相对路径 |
步骤 2:在主扩展文件中注册
重要: 创建插件专属扩展文件后,必须阅读 wix-cli-extension-registration,并按照“应用注册”部分更新 src/extensions.ts。
如果未完成步骤 2,该网站插件将不会显示在插件浏览器中。
结账插件
如果你正在为结账页面构建插件,该页面可能不支持在安装时自动添加插件。你必须创建一个控制面板页面,让用户能够将插件添加到自己的网站。有关控制面板页面的实现模式,请参阅 EXAMPLES.md。
示例
有关包含所有三个必需文件(插件组件、设置面板和扩展配置)的完整示例,请参阅 EXAMPLES.md。
示例用例:
- 畅销商品徽章 - 可在商品页面上自定义文字和颜色的徽章
- 预订确认信息 - 预订页面上的自定义确认信息
- 商品评价摘要 - 显示星级和评价数量
- 数据驱动插件 - 集成 Wix Data API 并处理编辑器环境的插件
最佳实践
实施指南
- 使用内联样式 - 自定义元素不支持 CSS 导入
- 处理编辑器环境 - 对依赖数据的插件,在编辑器模式下显示占位内容
- 不要调用
define()- Wix 会自动为你处理customElements.define() - 验证所有输入 - 检查必需的属性是否存在
- 遵循命名约定 - 所有属性和 widget API 均使用 kebab-case
- 聚焦插件功能 - 每个插件都应专注做好一件事
- 在多个插槽中测试 - 如果支持多个位置,请逐一测试
- 同时支持两个版本的 Stores - 为 Wix Stores 新旧商品页面都添加位置配置,以获得最大兼容性
编辑器沙盒
在编辑器中渲染站点插件时,它们会被置于沙盒中。这意味着它们会被视为来自不同的域名,从而影响对浏览器存储 API 的访问。
编辑器中受限的 API:
localStorage和sessionStorage(Web Storage API)document.cookie(Cookie Store API)- IndexedDB API
- Cache API
如何处理沙盒限制:
在访问受限的 API 之前,使用 @wix/site-window 中的 viewMode() 函数检查当前模式:
import { window as wixWindow } from '@wix/site-window';
const viewMode = await wixWindow.viewMode();
if (viewMode === 'Site') {
const item = localStorage.getItem('myKey');
} else {
// Mock storage or modify API usage for editor mode
}
在站点插件中使用 Wix SDK 模块
站点插件可以直接导入和使用 Wix SDK 模块——无需使用 createClient()。Wix 运行时会自动提供客户端上下文。
// ✅ CORRECT — Import SDK modules directly
import { items } from "@wix/data";
import { currentCart } from "@wix/ecom";
import { products } from "@wix/stores";
class MyPlugin extends HTMLElement {
async loadData() {
// Call SDK methods directly — no createClient needed
const result = await items.query("MyCollection").find();
const cart = await currentCart.getCurrentCart();
const productList = await products.queryProducts().limit(10).find();
}
}
// ❌ WRONG — Do NOT use createClient in site plugins
import { createClient } from "@wix/sdk";
const wixClient = createClient({ modules: { items, products } });
await wixClient.items.query(...); // Wrong — API surface differs through client
性能注意事项
- 尽量减小包体积——插件会在面向用户的页面上加载
- 避免在初始渲染时进行大量计算
- 尽可能延迟加载数据
- 使用高效的重新渲染模式