官方组件不够用时,自己做一个,推到站点,然后在 Markdown 里按名字调用。构建发生在你自己的机器上;站点只保存产物,不执行你的源码。
最快的路:jdu widget create
jdu widget create StarRating --template vanilla # vanilla | react | vue;react 默认走平台 runtime(产物几 KB)
cd star-rating && npm install
npm run dev # 本地预览:改代码自动刷新、暗色切换、sampleData 即时生效
npm run push # 构建并发布
生成的目录里契约类型、框架基类、构建脚本全部内联,改 src/types.ts 里的 Data 接口,npm run build 会自动生成 widget.json 的 dataSchema(字段 JSDoc 变成 description)。下面是这条路背后的东西,想手工搭也行。
一份最小实现
一个 Widget 就是一个目录,里面至少有:
widget.json:名字、scope、版本、入参的 JSON Schemaindex.js:自包含 ESM,默认导出一个无参可构造的 classindex.css:可选
widget.json:
{
"name": "StarRating",
"scope": "personal",
"version": "1.0.0",
"description": "星级评分:0–max 颗星,适合评审打分、满意度",
"dataSchema": {
"type": "object",
"required": ["value"],
"properties": {
"value": { "type": "number" },
"max": { "type": "number", "default": 5 }
}
},
"sampleData": { "value": 3, "max": 5 }
}
description 可选,一句话说什么场景用:/api/syntax 会带上它,agent 的 get_syntax 也靠它在几个组件之间做选择。
index.js(vanilla,零框架;源码即产物):
export default class StarRating {
mount(el, ctx) {
const { value, max = 5 } = ctx.data;
el.textContent = "★".repeat(Math.min(value, max));
el.style.color = "#f59e0b";
}
}
契约:
- 默认导出 class,
new时不做事 - 必须有
mount(el, ctx);副作用全放这里 - 可选
update(ctx)(主题切换、流式补数据)、destroy()(清定时器) ctx.data就是 fence 里的 JSON。未声明流式时,mount 时数据完整且已过dataSchema校验,不用再写守卫- 只有处理半截 JSON 时才加
static __widgetConfig = { streaming: true }
scope 填 personal(自己用);official 只有站点管理员能推。
有依赖时再打包
用到 npm 包或 TypeScript 时,打成浏览器 ESM,不要 external 任何东西,产物里也不能出现 node: 内置模块:
npx esbuild index.ts --bundle --format=esm --target=es2022 \
--minify --platform=browser \
--define:process.env.NODE_ENV='"production"' \
--outfile=dist/index.js
cp widget.json dist/
有 CSS 就一并放进 dist/index.css。然后:
jdu widget push dist
jdu widget list
<dir> 必须直接含 widget.json 和 index.js。推的是构建产物目录,不是源码根目录。
目录里如果有 src/,会随产物一起上传,方便以后回头看;不会经 HTTP 提供给读者。
在文档里调用
```widget:StarRating
{ "value": 4 }
```
裸名优先用你自己的 personal 实现。别人要钉死官方的同名组件,需要写成 widget:official/StarRating(你这个例子没有官方版,写了会降级)。
上线、下线、审批
引用格式照 jdu widget list 抄:personal/StarRating@1.0.0。
jdu widget disable personal/StarRating@1.0.0
jdu widget enable personal/StarRating@1.0.0
下线不删产物。文档不必重渲:读者下次打开时,这一块变成代码块。再上线同样即时生效。
| status | 含义 |
|---|---|
active | 可解析 |
pending | 站点开了审批时,team / personal 新推送的落地状态 |
disabled | 主动下线 |
rejected | 审批驳回 |
非 active 一律按未注册处理。管理员用 jdu widget approve / reject。重推不会改变 status:已经下线的,再 push 同一版本不会悄悄上线。
React 作者
不想手写 class 时,用 @monoedge/jdu-render/react 的工厂,一行接上契约:
import { reactWidget } from "@monoedge/jdu-render/react";
function StarRating({ props }) {
const value = Number(props?.value ?? 0);
return `★`.repeat(value);
}
export default reactWidget(StarRating);
update 走 React 的重新渲染,不要在 update 里自己 unmount 再 mount。官方组件就是这么接的。
构建时把 React 打进产物,或走站点提供的平台档 runtime(体积更小,同页共享一份)。第一份自己的组件用全内联最省事。
站点会拒绝的产物
push 时服务端做静态检查,不执行代码:
- 产物里 import 了 Node 内置模块
index.js/index.css/dataSchema超过体积上限dataSchema不是能编译的 JSON Schema
浏览器里执行失败(ESM 拉取失败、mount 抛错)只影响这一块,降级成代码块。
调 JSON 不必反复 push 文档:jdu widget dev 本地预览,调好了再推。