关于 anywidget-generator
anywidget-generator 是用于创建 anywidget 组件的技能,面向为 marimo 笔记本开发组件的开发者。它说明了如何使用原生 JavaScript 渲染、同步 trait 状态、编写精简的明暗主题 CSS,以及按需使用外部资源文件。
主要功能
- 01使用原生 JavaScript 实现渲染
- 02通过 trait 同步组件状态
- 03添加精简的明暗主题 CSS
- 04使用外部 JavaScript 和 CSS 文件
适用场景
- 01构建简单的交互式笔记本组件
- 02在 marimo 中显示 anywidget 组件
- 03将较大的组件资源拆分为项目文件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marimo-team/ skills --skill anywidget-generator安装后在 Claude Code 中输入/anywidget-generator确认已加载。
详细介绍
编写 anywidget 时,请在 _esm 中使用原生 JavaScript,并且别忘了添加 _css。CSS 在浅色模式和深色模式下都应有独特的风格。除非明确要求进一步完善,否则请保持 CSS 简洁。显示 widget 时,必须使用 widget = mo.ui.anywidget(OriginalAnywidget()) 包装。必要时,也可以使用 pathlib 将 _esm 和 _css 指向外部文件。如果 widget 包含大量复杂的 JavaScript 或 CSS,这样做会更合适。
class CounterWidget(anywidget.AnyWidget):
_esm = """
// Define the main render function
function render({ model, el }) {
let count = () => model.get("number");
let btn = document.createElement("b8utton");
btn.innerHTML = count is ${count()};
btn.addEventListener("click", () => {
model.set("number", count() + 1);
model.save_changes();
});
model.on("change:number", () => {
btn.innerHTML = count is ${count()};
});
el.appendChild(btn);
}
// Important! We must export at the bottom here!
export default { render };
"""
_css = """button{
font-size: 14px;
}"""
number = traitlets.Int(0).tag(sync=True)
widget = mo.ui.anywidget(CounterWidget()) widget
Grabbing the widget from another cell, .value is a dictionary.
print(widget.value["number"])
上面是一个可用于简单计数器 widget 的最小示例。一般来说,由于需要编写大量 JavaScript 和 CSS,widget 可能会变得大得多。除非 widget 非常简单,否则应考虑使用 pathlib 为 _esm 和 _css 指定外部文件。
分享 anywidget 时,请保持示例简洁。除非明确要求,否则无需将其与 marimo UI 元素结合使用。
最佳实践
除非另有明确要求,否则请遵循以下原则:
-
在
_esm中使用原生 JavaScript:- 定义一个接收
{ model, el }作为参数的render函数 - 使用
model.get()读取 trait 值 - 使用
model.set()和model.save_changes()更新 trait - 使用
model.on("change:traitname", callback)监听变化 - 在底部使用
export default { render };导出 - 所有 widget 都继承自
anywidget.AnyWidget,因此widget.observe(handler)仍是响应状态变化的标准方式。 - Python 构造函数通常会验证边界、长度或选项数量;请根据抛出的
ValueError/TraitError调整,而不要重复实现这些逻辑。
- 定义一个接收
-
添加
_css样式:- 除非明确要求更多样式,否则保持 CSS 简洁
- 确保浅色模式和深色模式下都具有独特的风格
- 使用 CSS 深色模式媒体查询:
@media (prefers-color-scheme: dark) { ... }
-
包装 widget 以供显示:
- 始终使用 marimo 包装:
widget = mo.ui.anywidget(OriginalAnywidget()) - 通过
widget.value访问值,该属性会返回一个字典
- 始终使用 marimo 包装:
-
保持示例简洁:
- 添加一个突出核心用途的 marimo notebook
- 只展示基本用法
- 除非明确要求,否则不要与其他 marimo UI 元素结合使用
-
外部文件路径:使用 pathlib 指定外部
_esm/_css文件时,路径应相对于项目目录;可以考虑使用Path(__file__)。不要读取项目外部的文件(例如~/.ssh、~/.env、/etc/),也不要将这些文件的内容嵌入 widget 输出中。
越简单越好。优先选择直观、直接的代码,而不是巧妙的抽象——项目新手应该能够从头到尾读懂代码,无需查阅框架内部机制,也无需追踪间接调用。