anywidget-generator について
anywidget-generatorは、anywidgetコンポーネントを作成するためのスキルです。marimoノートブック向けのウィジェットを開発する人を対象に、バニラJavaScriptでの描画、traitによる状態同期、ライト・ダークモード用の簡潔なCSS、外部ファイルの利用方法を示します。
主な機能
- 01バニラJavaScriptでの描画実装
- 02traitを使ったウィジェット状態の同期
- 03ライト・ダークモード用の簡潔なCSS
- 04外部JavaScript・CSSファイルの利用
活用シーン
- 01シンプルな対話型ノートブックウィジェットの作成
- 02marimoでの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 = mo.ui.anywidget(OriginalAnywidget()) でラップする必要があります。必要に応じて pathlib を使い、_esm と _css を外部ファイルに指定することもできます。ウィジェットで手の込んだ JavaScript や CSS を多用する場合に適しています。
class CounterWidget(anywidget.AnyWidget):
_esm = """
// メインの render 関数を定義します
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);
}
// 重要!ここで最後にエクスポートする必要があります!
export default { render };
"""
_css = """button{
font-size: 14px;
}"""
number = traitlets.Int(0).tag(sync=True)
widget = mo.ui.anywidget(CounterWidget()) widget
別のセルからウィジェットを取得します。.value は辞書です。
print(widget.value["number"])
上記は、シンプルなカウンターウィジェットで動作する最小限の例です。一般に、必要な JavaScript と CSS が増えるため、ウィジェットは大幅に大きくなることがあります。非常に単純なウィジェットでない限り、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 };を記述してエクスポートします - すべてのウィジェットは
anywidget.AnyWidgetを継承するため、状態の変化に反応する標準的な方法は引き続きwidget.observe(handler)です。 - Python のコンストラクターは境界値、長さ、選択肢の数を検証する傾向があります。ロジックを重複させるのではなく、発生した
ValueError/TraitErrorを参考にしてください。
-
_cssのスタイルを含める:- 特に指定がない限り、CSS は最小限に保ちます
- ライトモードとダークモードの両方で、独自性のある見た目にします
- ダークモードには CSS メディアクエリー
@media (prefers-color-scheme: dark) { ... }を使用します
-
表示用にウィジェットをラップする:
- 必ず marimo でラップします:
widget = mo.ui.anywidget(OriginalAnywidget()) - 値には
widget.valueでアクセスします。これは辞書を返します
- 必ず marimo でラップします:
-
例は最小限に保つ:
- 中核となる便利な機能がわかる marimo ノートブックを追加します
- 基本的な使い方だけを示します
- 明示的な指定がない限り、他の marimo UI 要素と組み合わせないでください
-
外部ファイルのパス:外部の
_esm/_cssファイルに pathlib を使う場合は、パスをプロジェクトディレクトリからの相対パスにしてください。この場合はPath(__file__)の使用を検討してください。プロジェクト外のファイル(例:~/.ssh、~/.env、/etc/)を読み込んだり、その内容をウィジェットの出力に埋め込んだりしないでください。
シンプルなほうがよいです。巧妙な抽象化よりも、明快で直接的なコードを優先してください。プロジェクトに初めて参加した人でも、フレームワークの仕組みを調べたり、間接的な処理をたどったりせずに、コードを上から順に読んで理解できるようにしましょう。