mcpField
S
svelte

svelte

github.com/vercel-labs开发18.8k1.2k 次安装5 天前更新
代码仓库

关于 svelte

@json-render/svelte 是用于 Svelte 5 的 json-render 渲染器,可将规范转换为 Svelte 组件树。它面向构建动态界面的开发者,支持状态、事件、验证及流式 UI。

主要功能

  1. 01将 UI 规范渲染为组件树
  2. 02通过目录和注册表定义组件
  3. 03支持状态驱动的可见性
  4. 04支持事件与内置动作
  5. 05支持动态属性和双向绑定
  6. 06流式渲染 UI 与聊天界面

适用场景

  1. 01将 JSON 规范呈现为 Svelte 界面
  2. 02根据状态控制元素显示
  3. 03将组件事件绑定到动作
  4. 04逐步呈现生成中的界面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/json-render --skill svelte

安装后在 Claude Code 中输入/svelte确认已加载。

详细介绍

@json-render/svelte

可将 json-render 规范转换为 Svelte 组件树的 Svelte 5 渲染器。

快速入门

<script lang="ts">
  import { Renderer, JsonUIProvider } from "@json-render/svelte";
  import type { Spec } from "@json-render/svelte";
  import Card from "./components/Card.svelte";
  import Button from "./components/Button.svelte";

  interface Props {
    spec: Spec | null;
  }

  let { spec }: Props = $props();
  const registry = { Card, Button };
</script>

<JsonUIProvider>
  <Renderer {spec} {registry} />
</JsonUIProvider>

创建目录

import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/svelte";
import { z } from "zod";

export const catalog = defineCatalog(schema, {
  components: {
    Button: {
      props: z.object({
        label: z.string(),
        variant: z.enum(["primary", "secondary"]).nullable(),
      }),
      description: "Clickable button",
    },
    Card: {
      props: z.object({ title: z.string() }),
      description: "Card container with title",
    },
  },
});

定义组件

组件应接受 BaseComponentProps<TProps>:

interface BaseComponentProps<TProps> {
  props: TProps; // Resolved props for this component
  children?: Snippet; // Child elements (use {@render children()})
  emit: (event: string) => void; // Fire a named event
  bindings?: Record<string, string>; // Map of prop names to state paths (for $bindState)
  loading?: boolean; // True while spec is streaming
}

<script lang="ts">
  import type { BaseComponentProps } from "@json-render/svelte";

  interface Props extends BaseComponentProps<{ label: string; variant?: string }> {}
  let { props, emit }: Props = $props();
</script>

<button class={props.variant} onclick={() => emit("press")}>
  {props.label}
</button>

<script lang="ts">
  import type { Snippet } from "svelte";
  import type { BaseComponentProps } from "@json-render/svelte";

  interface Props extends BaseComponentProps<{ title: string }> {
    children?: Snippet;
  }

  let { props, children }: Props = $props();
</script>

<div class="card">
  <h2>{props.title}</h2>
  {#if children}
    {@render children()}
  {/if}
</div>

创建注册表

import { defineRegistry } from "@json-render/svelte";
import { catalog } from "./catalog";
import Card from "./components/Card.svelte";
import Button from "./components/Button.svelte";

const { registry, handlers, executeAction } = defineRegistry(catalog, {
  components: {
    Card,
    Button,
  },
  actions: {
    submit: async (params, setState, state) => {
      // handle action
    },
  },
});

规范结构(元素树)

Svelte schema 使用元素树格式:

{
  "root": "card1",
  "elements": {
    "card1": {
      "type": "Card",
      "props": { "title": "Hello" },
      "children": ["btn1"]
    },
    "btn1": {
      "type": "Button",
      "props": { "label": "Click me" }
    }
  }
}

可见性条件

在元素上使用 visible,根据状态显示或隐藏元素:

  • { "$state": "/path" } - 检查值是否为真
  • { "$state": "/path", "eq": value } - 检查是否相等
  • { "$state": "/path", "not": true } - 检查值是否为假
  • { "$and": [cond1, cond2] } - AND 条件
  • { "$or": [cond1, cond2] } - OR 条件

Providers(通过 JsonUIProvider)

JsonUIProvider 组合所有上下文。各个上下文如下:

上下文用途
StateContext在组件之间共享状态(JSON Pointer 路径)
ActionContext处理通过事件系统分发的操作
VisibilityContext根据状态启用条件渲染
ValidationContext表单字段校验

事件系统

组件使用 emit 触发命名事件。元素的 on 字段将事件映射到操作绑定:


<script lang="ts">
  import type { BaseComponentProps } from "@json-render/svelte";

  interface Props extends BaseComponentProps<{ label: string }> {}

  let { props, emit }: Props = $props();
</script>

<button onclick={() => emit("press")}>{props.label}</button>
{
  "type": "Button",
  "props": { "label": "Submit" },
  "on": { "press": { "action": "submit" } }
}

内置操作

setState 操作会自动处理并更新状态模型:

{
  "action": "setState",
  "actionParams": { "statePath": "/activeTab", "value": "home" }
}

其他内置操作:pushState、removeState、push、pop。

动态属性和双向绑定

嵌套列表可以在外层 repeat 内将 repeat.statePath 设为 { "$item": "field" }。

组件接收属性前会解析以下表达式形式:

  • {"$state": "/state/key"} - 从状态中读取
  • {"$bindState": "/form/email"} - 从状态中读取并回写
  • {"$bindItem": "field"} - 为 repeat 项读取并回写
  • {"$cond": <condition>, "$then": <value>, "$else": <value>} - 条件值

要在组件内使用可写绑定,请使用 getBoundProp:

<script lang="ts">
  import { getBoundProp } from "@json-render/svelte";
  import type { BaseComponentProps } from "@json-render/svelte";

  interface Props extends BaseComponentProps<{ value?: string }> {}
  let { props, bindings }: Props = $props();

  let value = getBoundProp<string>(
    () => props.value,
    () => bindings?.value,
  );
</script>

<input bind:value={value.current} />

上下文辅助函数

推荐使用的辅助函数:

  • getStateValue(path) - 返回 { current }(可读写)
  • getBoundProp(() => value, () => bindingPath) - 返回 { current }(绑定时可读写)
  • isVisible(condition) - 返回 { current }(布尔值)
  • getAction(name) - 返回 { current }(已注册的处理程序)

高级上下文访问:

  • getStateContext()
  • getActionContext()
  • getVisibilityContext()
  • getValidationContext()
  • getOptionalValidationContext()
  • getFieldValidation(ctx, path, config?)

流式 UI

使用 createUIStream 流式传输规范:

<script lang="ts">
  import { createUIStream, Renderer } from "@json-render/svelte";

  const stream = createUIStream({
    api: "/api/generate-ui",
    onComplete: (spec) => console.log("Done", spec),
  });

  async function generate() {
    await stream.send("Create a login form");
  }
</script>

<button onclick={generate} disabled={stream.isStreaming}>
  {stream.isStreaming ? "Generating..." : "Generate UI"}
</button>

{#if stream.spec}
  <Renderer spec={stream.spec} {registry} loading={stream.isStreaming} />
{/if}

使用 createChatUI 处理聊天和 UI 响应:

const chat = createChatUI({ api: "/api/chat-ui" });
await chat.send("Build a settings panel");

评论

支持 Markdown