插件开发
插件给 BangNiCMS 加单一功能——客服浮窗、订阅、统计、轮播图。本文教开发。
my-plugin/├── manifest.json 必填:插件元数据├── README.md├── plugin.config.ts 插件配置 schema├── src/│ ├── index.ts 插件入口│ ├── components/ 挂到 slot 的 React 组件│ │ └── HeroCarousel.tsx│ ├── hooks/ 生命周期 hooks│ │ ├── onMount.ts│ │ └── onInquirySubmit.ts│ └── injections/ 全局注入(如 GA 脚本)│ └── headInjection.ts└── package.jsonmanifest.json
Section titled “manifest.json”{ "extensionType": "plugin", "extensionKey": "hero-carousel", "name": "首页轮播图", "version": "1.0.0", "author": "Your Name", "description": "...", "compatibility": { "bangnicms": ">=1.0.0" }, "supportedSlots": ["homepage-hero", "homepage-features"], "globalInjections": false, "hooks": ["onMount", "onInquirySubmit"]}A. Slot 渲染插件
Section titled “A. Slot 渲染插件”挂到主题的 slot 上展示内容:
import { definePlugin } from '@bangnicms/sdk';import HeroCarousel from './components/HeroCarousel';
export default definePlugin({ slots: { 'homepage-hero': HeroCarousel, 'homepage-features': HeroCarousel, },});import { usePluginConfig } from '@bangnicms/sdk';
export default function HeroCarousel() { const config = usePluginConfig('hero-carousel'); const { frames, autoplay, interval } = config;
return ( <div className="carousel"> {frames.map((frame, i) => ( <div key={i} className="frame"> <img src={frame.image} alt={frame.alt} /> <h2>{frame.title}</h2> <a href={frame.ctaUrl}>{frame.ctaText}</a> </div> ))} </div> );}B. 全局注入插件
Section titled “B. 全局注入插件”往所有页面 <head> 注入:
import { defineInjection } from '@bangnicms/sdk';
export default defineInjection({ position: 'head', render: (config) => ` <script async src="https://www.googletagmanager.com/gtag/js?id=${config.measurementId}"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '${config.measurementId}'); </script> `,});C. Hook 触发插件
Section titled “C. Hook 触发插件”监听系统事件触发逻辑:
import { defineHook } from '@bangnicms/sdk';
export default defineHook({ event: 'inquiry.created', handler: async (inquiry, config) => { // 发到 CRM await fetch(config.crmEndpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${config.apiKey}` }, body: JSON.stringify({ firstName: inquiry.contactName, email: inquiry.email, company: inquiry.company, }), }); },});配置 schema
Section titled “配置 schema”plugin.config.ts 声明运营可调字段:
import { definePluginConfig } from '@bangnicms/sdk';
export default definePluginConfig({ basic: { enabled: { type: 'boolean', default: true, label: '启用' }, }, frames: { type: 'array', minItems: 1, maxItems: 5, item: { image: { type: 'image', label: '背景图' }, title: { type: 'localized-text', label: '主标题' }, subtitle: { type: 'localized-text', label: '副标题' }, ctaText: { type: 'localized-text', label: 'CTA 按钮文字' }, ctaUrl: { type: 'url', label: 'CTA 链接' }, }, }, autoplay: { type: 'boolean', default: true }, interval: { type: 'number', default: 5, min: 1, max: 30 },});后台「插件 → 设置」自动渲染表单。
字段类型 localized-text 自动支持多语言:
// 配置:{ title: { type: 'localized-text', default: { 'zh-CN': '春节大促', 'en': 'Spring Festival Sale' } }}
// 使用:const title = config.title[currentLang] || config.title[defaultLang];多次挂载(独立配置)
Section titled “多次挂载(独立配置)”如果插件支持挂多次(每次配置独立):
const config = usePluginConfig('hero-carousel', { instanceId: slotInstanceId });每次挂载实例有独立 instanceId → 配置不混。
与主题的协作
Section titled “与主题的协作”主题预留 slot,插件挂上去——双方完全解耦:
- 主题不知道用户装了什么插件
- 插件不知道主题是哪个版本
通过Slot API 通讯。
插件代码注入到前台 → 必须高性能:
- 体积小(< 50 KB)
- 异步加载(不阻塞 LCP)
- 懒加载(仅当 slot 进入视口才加载)
- 不阻塞主线程
- 不在前端暴露 API Key(如果需要 API 调用,通过后端代理)
- 用户输入校验(防 XSS)
- 第三方脚本:用 SRI(subresource integrity)
- 本地:本地运行 BangNiCMS + 加载插件
- 单元测试:测组件渲染 / hook 逻辑
- 集成测试:在测试 BangNiCMS 实例上传 zip 测试
- 多主题:换不同主题测试 slot 兼容
pnpm buildzip -r my-plugin-v1.0.0.zip dist manifest.json README.md详见 打包与发布。