@chuchen3d/wallpaper-plugin-sdk
🧩 @chuchen3d/wallpaper-plugin-sdk
初尘壁纸插件开发工具包 — 创建、预览、打包 .yizirwp 壁纸插件。
- CLI:
chuchen-plugin init / build / validate / example - 程序化调用:
import { ... } from '@chuchen3d/wallpaper-plugin-sdk'
v1.1.0 起为真隔离沙箱模型:插件运行在宿主创建的跨源 iframe 沙箱内, 构建产物自包含(Vue/Three 全部打进 IIFE bundle,不依赖宿主注入的运行时), manifest 新增
apiVersion与network.domains字段,联网必须在 manifest 声明。 详见下文 沙箱运行时。组件 props 契约不变,存量组件代码零改动, 用新 SDK 重跑build即完成迁移。
安装
# 全局安装
npm install -g @chuchen3d/wallpaper-plugin-sdk
# 或从私有仓库安装
npm install -g @chuchen3d/wallpaper-plugin-sdk --registry=https://registry.yiziran.com.cn/
运行时依赖:Node ≥ 16。项目内需
vue ^3.4、vite ^5.2、@vitejs/plugin-vue ^5.0;使用 Three.js 时需three ^0.162(可选,会随包打进 bundle,zip 体积 +150~250KB 属正常)。
快速开始
chuchen-plugin init my-wallpaper # 初始化项目(manifest 自动带 apiVersion/network 字段)
cd my-wallpaper
npm install
npm run dev # 启动预览,浏览器右侧实时调节参数
# 编辑 MyWallpaper.vue 实现动画
chuchen-plugin build # 打包为自包含 my-wallpaper-1.0.0.yizirwp
将生成的 .yizirwp 文件在「初尘壁纸 → 功能面板 → 开发者中心」中上传插件即可,之后管理员审核后即可在壁纸商店中下载。
CLI 命令
| 命令 | 说明 |
|---|---|
chuchen-plugin init <项目名> |
初始化壁纸插件项目 |
chuchen-plugin build |
编译 Vue SFC 并打包为 .yizirwp |
chuchen-plugin validate |
校验 manifest.json |
chuchen-plugin example [项目名] |
生成完整示例项目(粒子之舞) |
init 选项
chuchen-plugin init my-wallpaper \
--name "我的壁纸" \
--description "一款精美的壁纸" \
--author "开发者" \
--category abstract \ # natural/tech/abstract/festival/utility
--three # 启用 Three.js 支持
build 选项
chuchen-plugin build \
--output my-plugin.yizirwp \ # 自定义输出文件名
--dry-run # 仅校验,不生成文件
预览视频/GIF(可选)
插件可以附带一段预览视频或 GIF,商店详情页会循环播放。
-
在项目根放一个预览文件,文件名必须是
preview.{mp4,webm,mov,gif}:cp ~/my-preview.mp4 ./preview.mp4 -
打包(SDK 自动检测并嵌入):
npx chuchen-plugin build # 或自定义文件名/路径: npx chuchen-plugin build --preview ./assets/preview.mp4 -
上传插件包到服务端。服务端首次上传时会自动抽取预览到独立存储。
-
替换预览:在 admin 后台 → 审核详情 → "替换预览",或客户端开发者中心 → "预览管理"。 不需要重新上传整个 .yizirwp。
文件约束
| 格式 | 扩展名 | 上限 |
|---|---|---|
| 视频 | mp4 / webm / mov | 10 MB |
| 动图 | gif | 5 MB |
注意:服务端 mime 推断只用扩展名,请确保文件扩展名与实际类型一致。
项目结构
my-wallpaper/
├── manifest.json # 插件元数据(必选)
├── MyWallpaper.vue # 壁纸组件(必选,文件名与 entry 对应)
├── package.json # 开发依赖(不打入包)
├── vite.config.js # Vite 配置(不打入包)
├── index.html # 预览入口(不打入包)
├── dev-preview.js # Mock props 注入脚本(不打入包)
├── thumbnail.png # 缩略图(可选,256×256 推荐,自动打入包)
└── README.md # 开发说明(不打入包)
manifest.json
完整示例
{
"schemaVersion": "2.0",
"id": "code-planet",
"name": "雷电",
"version": "1.0.2",
"author": { "name": "忆自然咖啡" },
"description": "一款精美的初尘壁纸插件-雷电",
"tags": ["自然", "奇景", "线状闪电"],
"category": "abstract",
"entry": "CodePlanet.vue",
"files": ["CodePlanet.vue"],
"params": {
"frequency": {
"type": "number", "default": 5, "min": 1, "max": 15, "step": 1,
"label": "闪电频率(秒)"
},
"intensity": {
"type": "number", "default": 1.0, "min": 0.1, "max": 2.0, "step": 0.1,
"label": "亮度强度"
},
"colorTheme": {
"type": "select", "default": "blue",
"options": ["blue", "purple", "white", "warm"],
"label": "颜色主题"
},
"cameraHeight": {
"type": "number", "default": 11, "min": 2, "max": 30, "step": 0.5,
"label": "相机高度",
"tier": "premium"
}
},
"previewBg": "linear-gradient(135deg, #f093fb 0%, #f5576c 100%)",
"emoji": "🎨",
"minAppVersion": "1.0.0",
"apiVersion": 1,
"network": { "domains": [] },
"isPremium": false,
"originalPrice": 19.9,
"price": 9.9,
"tier": "yearly"
}
字段说明
元数据(基础)
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
schemaVersion |
string | ✅ | Schema 版本(2.0) |
id |
string | ✅ | 插件 ID,kebab-case(如 my-wallpaper) |
name |
string | ✅ | 显示名称 |
version |
string | ✅ | 语义化版本(如 1.0.0) |
author |
object | ✅ | { name, email? } |
entry |
string | ✅ | 入口 .vue 文件名 |
files |
string[] | ✅ | 打包文件列表(构建产物由 index.js 替代,此字段仅源码声明) |
minAppVersion |
string | ✅ | 最低应用版本 |
展示(推荐)
| 字段 | 类型 | 说明 |
|---|---|---|
description |
string | 描述 |
tags |
string[] | 标签数组 |
category |
string | 分类:natural / tech / abstract / festival / utility |
previewBg |
string | 商店预览背景(CSS 渐变) |
emoji |
string | Emoji 图标 |
商业化(可选)
| 字段 | 类型 | 说明 |
|---|---|---|
isPremium |
boolean | 是否付费插件 |
price |
number | 售价(非负) |
originalPrice |
number | 原价(非负,用于划线价) |
tier |
string | 订阅档位:free / monthly / yearly / lifetime |
沙箱契约(SDK ≥1.1.0,init 模板自动生成)
| 字段 | 类型 | 说明 |
|---|---|---|
apiVersion |
number | 宿主插件 API 大版本,当前为 1。宿主按它做版本门禁:包要求版本高于宿主支持时拒绝加载并提示用户升级应用。init 生成的模板自带,勿手改;存量项目手动补 "apiVersion": 1 即可 |
network |
object | { domains: string[] },≤8 个 https 域名(裸域名自动补 https://,带 path 会被截断)。空数组/缺省 = 默认全禁网:沙箱 CSP 只放行声明域名,fetch/XHR/WebSocket/图片/音频/字体等一切远程请求访问未声明域名都会被浏览器拦截 |
联网壁纸示例(如拉取天气 API):
"apiVersion": 1,
"network": { "domains": ["https://devapi.qweather.com"] }
validate / build 会校验:apiVersion 为 ≥1 的整数(缺省合法,视为 v1);
network 为对象、domains 为合法 https 域名的字符串数组且 ≤8 项。
声明与代码不符的包会在服务端审核的风险报告中标记(未声明域名 / 声明未使用)。
params 字段
params 是「参数 key → 定义」的对象,key 即组件中 paramValues 的读取键。支持四种控件类型:
| type | 字段 | 控件 |
|---|---|---|
number |
default / min / max / step / label |
滑块 |
select |
default / options / label |
下拉 |
toggle |
default / label |
开关 |
color |
default / label |
颜色选择器 |
- 所有类型均可选填
tier(如"premium"),标记为会员专属参数。 select的options支持两种写法:- 字符串数组(简写):
["blue", "purple", "white"] - 对象数组(带显示标签):
[{ "value": "rainbow", "label": "彩虹" }]
- 字符串数组(简写):
{
"speed": { "type": "number", "default": 1.0, "min": 0.1, "max": 3.0, "step": 0.1, "label": "动画速度" },
"colorPreset": {
"type": "select", "default": "rainbow",
"options": [
{ "value": "rainbow", "label": "彩虹" },
{ "value": "ocean", "label": "海洋蓝" }
],
"label": "配色方案"
},
"mouseInteraction": { "type": "toggle", "default": true, "label": "鼠标交互" },
"bgColor": { "type": "color", "default": "#0c0c1d", "label": "背景色" }
}
打包时 SDK 会把
params自动转换为{ defaults, definitions }格式,开发者保持扁平写法即可。
沙箱运行时(v1.1.0 起)
宿主把 .yizirwp 解包后,让插件运行在 sandbox="allow-scripts" 的跨源 iframe(opaque origin)内。这是浏览器层面的隔离保证:
| 能力 | 状态 | 替代方案 |
|---|---|---|
| 宿主 localStorage / cookie | ❌ 不可达(访问直接抛 SecurityError) | __YIZIRAN_PLUGIN_API__.storage(见下) |
window.parent / window.top(宿主 DOM/位置/存储) |
❌ 跨源拦截 | ——(插件不应针对宿主) |
Tauri IPC(__TAURI__ / __TAURI_IPC__) |
❌ 沙箱内不存在 | ——(插件无系统能力) |
| 联网(fetch/XHR/WebSocket/图片/音频/字体) | ⚠️ 仅放行 manifest network.domains 声明的域名 |
未声明域名一律被沙箱 CSP 拦截 |
window.open 弹窗 |
❌ 禁止 | —— |
| 与宿主通信 | 仅 postMessage 协议 | SDK 入口已封装,勿直接调 postMessage |
插件内持久化(storage KV)
沙箱内 localStorage 不可用。需要持久化(API key 缓存、用户偏好等)时使用宿主代理的 KV 存储:
const api = window.__YIZIRAN_PLUGIN_API__
await api.storage.set('myKey', { a: 1 })
const val = await api.storage.get('myKey') // null = 不存在
配额:单值 ≤16KB、键数 ≤32、总量 ≤64KB;按插件 ID 命名空间隔离,卸载插件时清空。
key 仅允许字母/数字/_/./-,长度 ≤64。
沙箱内可用的宿主 API(window.__YIZIRAN_PLUGIN_API__)
参数与性能策略由 SDK 入口包装器自动接入组件 props(paramValues / performancePolicy),
通常无需直接调用;以下按需使用:
| 方法 | 说明 |
|---|---|
storage.get(key) / storage.set(key, value) |
宿主侧 KV 持久化(Promise) |
getMousePosition() |
{x, y} 或 null |
getContainerSize() |
{width, height} |
setCaptureHandler(fn) |
覆盖默认截图:fn 返回 { dataUrl, width, height }(多 canvas 场景用) |
getPluginId() |
当前插件 ID |
log(level, ...args) / trackEvent(name, data) |
日志转发宿主 / 埋点桥接 |
Vue 组件规范
必需 Props
宿主通过入口包装器向组件注入 3 个 props:
<script setup>
const props = defineProps({
/** 容器 DOM 引用(取尺寸、绑事件) */
containerRef: { type: Object, default: null },
/** 用户调节的参数值,key 与 manifest.params 一一对应,实时更新 */
paramValues: { type: Object, default: () => ({}) },
/** 性能策略:fpsLimit(0=暂停)、pixelRatioCap、mouseInteraction */
performancePolicy: { type: Object, default: () => ({}) },
})
</script>
生命周期骨架
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const canvasRef = ref(null)
let animationId = null
/** 安全读取参数(带默认值回退) */
function getParam(key, defaultValue) {
const val = props.paramValues?.[key]
return val !== undefined ? val : defaultValue
}
onMounted(() => {
init() // 建场景、绑事件、启动动画
})
onUnmounted(() => {
if (animationId) cancelAnimationFrame(animationId)
// 移除事件监听;释放 GPU 资源(dispose / forceContextLoss)
})
function animate() {
animationId = requestAnimationFrame(animate)
const fpsLimit = props.performancePolicy?.fpsLimit ?? 60
if (fpsLimit === 0) return // 暂停渲染
// 读取参数并绘制
}
</script>
<template>
<canvas ref="canvasRef" class="wallpaper-canvas"
style="width:100%;height:100%;display:block;"></canvas>
</template>
★ 样式必须内联在模板上(
style="..."):构建时<style scoped>会输出到dist/style.css,而打包产物只含index.js—— 样式表不进包,壁纸尺寸/布局会 整个丢掉。动态样式用 JS 设置el.style.xxx。
Three.js 壁纸
three随包打进自包含 IIFE bundle(v1.1.0 起不再依赖宿主注入的运行时),组件内import * as THREE from 'three'即可。- 项目
package.json中three版本对齐^0.162,避免本地预览与打包产物 API 不一致。 - 着色器以内联 JS 模板字面量编写,不需要
vite-plugin-glsl。 onUnmounted必须 dispose 全部 geometry / material / texture,并renderer.forceContextLoss();沙箱在壁纸移除时会销毁整个 iframe 上下文兜底,但优雅清理仍是组件的责任。
截图兼容性要求
宿主「截图与分享」面板依赖以下契约才能正确截取插件画面。强烈建议遵循 —— 不满足时截图会失败或得到空白图。
| 要求 | 必填 | 说明 |
|---|---|---|
| canvas 是组件模板内的第一个 canvas | ✅ | 宿主截图协议取沙箱 iframe 内 querySelector('canvas') 的第一个元素。多 canvas 场景用 __YIZIRAN_PLUGIN_API__.setCaptureHandler(fn) 指定(fn 返回 { dataUrl, width, height })。class wallpaper-canvas 建议保留(兼容旧宿主回退路径) |
WebGL renderer 设 preserveDrawingBuffer: true |
✅ | Three.js: new THREE.WebGLRenderer({ canvas, preserveDrawingBuffer: true })。否则 canvas.toBlob() 拿到的 PNG 是空白/透明帧(不是错误,但图像无效)。 |
| 不加载无 CORS 头的跨域图片 | ✅ | 否则 canvas 被 taint,canvas.toBlob() 抛 SecurityError。使用跨域纹理时需服务器返回 Access-Control-Allow-Origin 头或设置 crossOrigin = 'anonymous',且该图片域名必须已声明进 manifest network.domains(沙箱 CSP 对 img-src 同样管控)。 |
| canvas 写在组件模板内 | ✅ | 标准流程自动满足——SDK 入口把 Vue 应用挂载到沙箱 iframe 内的铺满 div 上。 |
Three.js 截图友好写法示例
import * as THREE from 'three'
function init() {
const canvas = canvasRef.value
const renderer = new THREE.WebGLRenderer({
canvas,
preserveDrawingBuffer: true, // ★ 让 canvas.toBlob() 能拿到帧
antialias: true,
alpha: true,
})
// ...
}
Canvas 2D 注意事项
如果用纯 Canvas 2D(无需 WebGL),preserveDrawingBuffer 不适用 —— toBlob 直接可用,但同样不要加载跨域图。
调试技巧
打开 DevTools,在 Console 执行(dev 预览页或宿主沙箱内均可):
const c = document.querySelector('canvas')
console.log(c?.toDataURL('image/png')?.length, 'bytes') // >1000 表示 canvas 有内容
若长度很小(< 1000),canvas 是空白 —— 检查 preserveDrawingBuffer 是否开启。
若壁纸布局/尺寸整个不对 —— 检查样式是否写在了 <style scoped> 里(不入包,改内联)。
若 fetch/图片报 CSP 拦截错误 —— 域名未声明进 manifest network.domains,声明后重新 build。
打包规则
打包 .yizirwp 时,自动排除以下文件(仅开发用):
node_modules/、.git/、.gitignore、.DS_Store、Thumbs.dbpackage.json、package-lock.json、yarn.lock、pnpm-lock.yamlvite.config.js、index.html、dev-preview.jsREADME.md、ICON_GUIDE.md
打包产物:manifest.json(重写 entry=index.js + params 格式转换)、index.js(自包含 IIFE bundle,内含整套 Vue/Three 运行时)、thumbnail.*(如有)、preview/<file>(如有)。
程序化 API
SDK 也支持在脚本中调用:
import {
cmdInit, cmdBuild, cmdValidate, cmdExample, // 命令
generateManifest, generateVueComponentTemplate, generateDevProjectFiles, // 模板
generatePluginEntry, // 沙箱入口包装器生成
validateManifest, convertParamsToAppFormat, // 工具
buildPlugin, packProject, packFromDist, // 构建/打包
VERSION, PACKAGE_NAME,
} from '@chuchen3d/wallpaper-plugin-sdk'
许可证
MIT
相关链接
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.1.0 | ^3.4.0 | MIT |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.0.0-stage |