Novu use_figma 技能参考:Figma Plugin API 常用脚本模式详解(common-patterns)
本文基于 Novu 仓库中 .agents/skills/figma-use/references/common-patterns.md 这份技能参考文档展开。它是 use_figma 技能(Figma Plugin API 自动化脚本执行)的"可直接复用的代码骨架库",覆盖脚本返回值结构、节点创建、自动布局、变量与模式(Modes)、组件变体、团队库导入以及大型组件集的多步拆分等 10 类高频操作。读完本文,你可以直接掌握在 Figma 文件上下文内编写、执行并串联多轮 Plugin API 脚本的完整套路,知道每种操作的正确代码形态、关键约束和常见陷阱。
文档定位:它是 use_figma 技能的"工作代码示例库"
common-patterns.md 是 use_figma 技能 参考文档集的一部分,在 SKILL.md 的参考文档索引中被定义为:
Need working code examples — Script scaffolds: shapes, text, auto-layout, variables, components, multi-step workflows
也就是说,它不是 API 字典(那是 plugin-api-standalone.d.ts 的职责),而是一组已经过验证、可直接复制修改的最小工作示例,每个示例解决一类高频场景:
| 章节(原文档) | 解决的问题 |
|---|---|
| Basic Script Structure | 脚本如何把数据传回调用方 |
| Create a Styled Shape / Text Node / Frame with Auto-Layout | 基础节点的创建与定位 |
| Create Variable Collections and Bindings | 创建多模式变量集并绑定到填充 |
| Create Components and Import by Key | 组件变体属性、团队库组件导入 |
| Component Sets with Variable Modes | 变体命名与变量模式联合使用 |
| Multi-Step Large ComponentSet Pattern | 50+ 变体的分多次调用拆分策略 |
| Read Existing Nodes and Return Data | 只读巡检脚本的写法 |
在 Novu 仓库中,该技能由 skills-lock.json 登记为来自 figma/mcp-server-guide 的同步技能(figma-use),说明其内容遵循 Figma 官方 MCP 工具链的约定。技能规则明确要求:每次调用 use_figma 工具前必须先加载该技能,并始终传 skillNames: "figma-use" 参数用于使用追踪(该参数不影响执行)。
理解这些示例前,先记住三条贯穿所有代码的硬规则(来自 SKILL.md 的 Critical Rules,也与本文所有示例的形态直接对应):
return是唯一输出通道。返回值被自动 JSON 序列化;console.log()的输出永远不会传回调用方,不要调用figma.closePlugin(),也不要自己包 async IIFE(代码会自动包在 async 上下文中,顶层await可直接用)。- 失败是原子的。脚本一旦报错就完全不执行、文件零改动,所以正确姿势是先读懂报错、修正脚本再重试,而不是盲目重试。
- 小步增量推进。每次调用最多做 10 个左右逻辑操作,创建/修改后把节点 ID 返回,作为下一次调用的输入——这正是下面"Basic Script Structure"存在的意义。
基本脚本结构:return 结构化的 ID 追踪
原文档给出的第一例是所有脚本的骨架:
const createdNodeIds = []
const mutatedNodeIds = []
// Your code here — track every node you create or mutate
// createdNodeIds.push(newNode.id)
// mutatedNodeIds.push(existingNode.id)
return {
success: true,
createdNodeIds,
mutatedNodeIds,
// Plus any other useful data for subsequent calls
count: createdNodeIds.length
}
要点拆解:
- 每个脚本维护两个数组:
createdNodeIds(本次新建的节点)与mutatedNodeIds(本次修改的既有节点)。这是硬性要求而非建议——后续调用要靠这些 ID 去引用、校验和清理这些节点。 - 返回值可以附带任意"对后续调用有用"的数据,如数量统计、集合 ID、模式 ID 等。
- 与 gotchas.md 中的反例对照:只返回父级 frame 的 ID、丢失子节点 ID 是典型错误;正确做法是把 frame、rect、text 三个 ID 都放进
createdNodeIds,并额外给出rootNodeId方便定位。
基础节点创建:先找"空地",再放节点
创建带样式的形状
所有"追加到当前页面"的示例都复用同一段空位扫描逻辑:
// Find clear space to the right of existing content
const page = figma.currentPage
let maxX = 0
for (const child of page.children) {
maxX = Math.max(maxX, child.x + child.width)
}
const rect = figma.createRectangle()
rect.name = "Blue Box"
rect.resize(200, 100)
rect.fills = [{ type: 'SOLID', color: { r: 0.047, g: 0.549, b: 0.914 } }]
rect.cornerRadius = 8
rect.x = maxX + 100 // offset from existing content
rect.y = 0
figma.currentPage.appendChild(rect)
return { nodeId: rect.id }
这段代码体现了几条关键约定:
- 新顶层节点默认落在 (0,0)。直接
appendChild到页面会让多个新节点互相堆叠、并盖住既有内容。因此先遍历figma.currentPage.children,用Math.max(child.x + child.width)求出最右边界,再以maxX + 100的间距放置。注意这只对"直接挂在页面上的顶层节点"必要;放进 frame 或自动布局容器的子节点由父级排布,无需扫描。 - 颜色是 0–1 浮点域而非 0–255,例如
{r: 0.047, g: 0.549, b: 0.914}表示一种蓝色;写成{r: 12, g: 140, b: 233}会直接触发校验报错。 - fills 是整体重赋值,不是原地修改——示例中
rect.fills = [...]一次性替换整个数组。 - 创建后返回
{ nodeId: rect.id },供后续调用引用。
创建文本节点
// Find clear space to the right of existing content
const page = figma.currentPage
let maxX = 0
for (const child of page.children) {
maxX = Math.max(maxX, child.x + child.width)
}
await figma.loadFontAsync({ family: "Inter", style: "Regular" })
const text = figma.createText()
text.characters = "Hello World"
text.fontSize = 16
text.fills = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }]
text.textAutoResize = 'WIDTH_AND_HEIGHT'
text.x = maxX + 100
text.y = 0
figma.currentPage.appendChild(text)
return { nodeId: text.id }
文本节点比形状多一条前置约束:figma.loadFontAsync() 必须先于任何文本操作。SKILL.md 的规则 8 强调,这不仅是"设置文字"之前,而是包括 appendChild、insertChild、setBoundVariable、setExplicitVariableModeForCollection 在内的任何触碰含未加载字体节点的操作之前。如果文档中已存在文本节点,建议在脚本开头用 await figma.listAvailableFontsAsync() 发现可用字体后预加载全部字体,完整的预加载模式见 gotchas.md。另外注意字体样式名必须与实际发布名一致,"SemiBold" 与 "Semi Bold" 的差异是经典踩坑点。
本例还展示了 textAutoResize = 'WIDTH_AND_HEIGHT'(宽高都随内容自适应)的用法:文本节点不需要手动 resize。
创建带自动布局的 Frame
// Find clear space to the right of existing content
const page = figma.currentPage
let maxX = 0
for (const child of page.children) {
maxX = Math.max(maxX, child.x + child.width)
}
const frame = figma.createAutoLayout('VERTICAL')
frame.name = "Card"
frame.primaryAxisAlignItems = 'MIN'
frame.counterAxisAlignItems = 'MIN'
frame.paddingLeft = 16
frame.paddingRight = 16
frame.paddingTop = 12
frame.paddingBottom = 12
frame.itemSpacing = 8
frame.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
frame.cornerRadius = 8
frame.x = maxX + 100
frame.y = 0
figma.currentPage.appendChild(frame)
return { nodeId: frame.id }
这里的关键 API 是 figma.createAutoLayout('VERTICAL')——它是 figma.createFrame() 的"一步到位"替代:创建出的 frame 已启用自动布局且两轴默认 HUG 内容。SKILL.md 明确建议"任何需要自动布局的容器都优先用 createAutoLayout,不要手写 layoutMode + primaryAxisSizingMode + layoutSizingX 的多步配置",因为这既啰嗦又容易踩顺序坑。该 API 在类型定义文件中也有正式声明,见 plugin-api-standalone.d.ts#L1083-L1093。
属性语义速读:
primaryAxisAlignItems = 'MIN':主轴(垂直方向)顶部对齐;counterAxisAlignItems = 'MIN':交叉轴(水平方向)左侧对齐;paddingLeft/Right/Top/Bottom = 16/16/12/12与itemSpacing = 8:内边距与子项间距,单位是 px 数值;- 创建后子节点可以直接设
layoutSizingHorizontal = 'FILL'撑满,前提是appendChild已完成(FILL必须在挂到自动布局父级之后设置,提前设置会抛错)。
变量系统:多模式集合与填充绑定
创建带多个模式的变量集合
const collection = figma.variables.createVariableCollection("Theme/Colors")
// Rename the default mode
collection.renameMode(collection.modes[0].modeId, "Light")
const darkModeId = collection.addMode("Dark")
const lightModeId = collection.modes[0].modeId
const bgVar = figma.variables.createVariable("bg", collection, "COLOR")
bgVar.setValueForMode(lightModeId, { r: 1, g: 1, b: 1, a: 1 })
bgVar.setValueForMode(darkModeId, { r: 0.1, g: 0.1, b: 0.1, a: 1 })
const textVar = figma.variables.createVariable("text", collection, "COLOR")
textVar.setValueForMode(lightModeId, { r: 0, g: 0, b: 0, a: 1 })
textVar.setValueForMode(darkModeId, { r: 1, g: 1, b: 1, a: 1 })
return {
collectionId: collection.id,
lightModeId,
darkModeId,
bgVarId: bgVar.id,
textVarId: textVar.id
}
流程解析:
createVariableCollection("Theme/Colors")创建集合,名称用斜杠表达层级;- 新集合自带一个默认模式,用
collection.renameMode(collection.modes[0].modeId, "Light")重命名为 Light,再collection.addMode("Dark")追加 Dark 模式; createVariable("bg", collection, "COLOR")创建颜色变量(第三个参数是变量类型,此处为COLOR),注意 SKILL.md 规则 11:createVariable的集合参数既可传对象也可传 ID 字符串,推荐传对象;- 每个模式用
setValueForMode(modeId, {r,g,b,a})独立赋值,实现 Light/Dark 双主题; - 把 collection、mode、variable 的 ID 全部 return——多步工作流的后续调用要靠这些字符串字面量取回对象。
补充一点 SKILL.md 规则 16 的要求:创建变量时应显式设置 variable.scopes(如背景用 ["FRAME_FILL", "SHAPE_FILL"]、文字色用 ["TEXT_FILL"]、间距用 ["GAP"]),因为默认的 ALL_SCOPES 会污染所有属性选择器。完整 scope 清单见 variable-patterns.md。
把颜色变量绑定到填充
const variable = await figma.variables.getVariableByIdAsync("VariableID:1:2")
const rect = figma.createRectangle()
const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }
// setBoundVariableForPaint returns a NEW paint — capture it!
const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", variable)
rect.fills = [boundPaint]
return { nodeId: rect.id }
这个示例浓缩了两条最容易被忽视的规则:
- 跨调用取变量:上一轮 return 出的
bgVarId以字符串字面量(如"VariableID:1:2")传入本轮,用await figma.variables.getVariableByIdAsync(id)恢复为对象。 setBoundVariableForPaint返回的是一个全新的 paint 对象,必须捕获返回值再赋给rect.fills。它不会修改basePaint本身。这是 SKILL.md 规则 10 的原文:"returns a NEW paint — must capture and reassign"。该 API 在类型定义中的声明位置见 plugin-api-standalone.d.ts#L2157。
basePaint 里的具体颜色值在这里只是占位——绑定后实际渲染颜色以变量值为准,所以示例统一写成黑色基座。
组件与变体:属性、导入与变量模式
带组件属性的变体创建
原文档在此节开头给出了一条总规则(加粗强调):组件属性(TEXT、BOOLEAN、INSTANCE_SWAP)必须在每个变体的循环体内、combineAsVariants 之前添加,组件集(combine 后的 COMPONENT_SET)会从子组件继承这些属性。完整示例:
await figma.loadFontAsync({ family: "Inter", style: "Regular" })
// Assume defaultIconComp is an existing icon component (discovered earlier)
const defaultIconComp = figma.getNodeById('ICON_COMPONENT_ID')
const components = []
const variants = ["primary", "secondary"]
for (const variant of variants) {
const comp = figma.createComponent()
comp.name = `variant=${variant}`
comp.layoutMode = 'HORIZONTAL'
comp.primaryAxisAlignItems = 'CENTER'
comp.counterAxisAlignItems = 'CENTER'
comp.paddingLeft = 12
comp.paddingRight = 12
comp.paddingTop = 8
comp.paddingBottom = 8
comp.layoutSizingHorizontal = 'HUG'
comp.layoutSizingVertical = 'HUG'
comp.cornerRadius = 6
comp.itemSpacing = 8
// TEXT property — label
const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Button')
const label = figma.createText()
label.characters = "Button"
label.fontSize = 14
comp.appendChild(label)
label.componentPropertyReferences = { characters: labelKey }
// BOOLEAN + INSTANCE_SWAP — icon slot
const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', false)
const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', defaultIconComp.id)
const iconInstance = defaultIconComp.createInstance()
comp.insertChild(0, iconInstance) // icon before label
iconInstance.componentPropertyReferences = {
visible: showIconKey,
mainComponent: iconSlotKey
}
components.push(comp)
}
const componentSet = figma.combineAsVariants(components, figma.currentPage)
componentSet.name = "Button"
// Layout variants in a row after combining (they stack at 0,0 by default)
const colW = 140
componentSet.children.forEach((child, i) => {
child.x = i * colW
child.y = 0
})
// Resize from actual child bounds — formula-based sizing is error-prone
let maxX = 0, maxY = 0
for (const c of componentSet.children) {
maxX = Math.max(maxX, c.x + c.width)
maxY = Math.max(maxY, c.y + c.height)
}
componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40)
return {
componentSetId: componentSet.id,
componentIds: components.map(c => c.id)
}
逐点解析:
- 命名约定
variant=${variant}:变体名即属性声明,primary/secondary会构成一个名为variant的属性轴。组件集排版代码正是靠解析这种key=value, key=value命名来定位网格坐标的。 - TEXT 属性:
comp.addComponentProperty('Label', 'TEXT', 'Button')返回值就是属性 key 字符串(形如"label#4:0",后缀不可预测),直接用于label.componentPropertyReferences = { characters: labelKey }把文本节点的characters关联到该属性。gotchas.md 专门用 WRONG/CORRECT 示例警告:不要猜测 key、不要把返回值当对象取Object.keys()(那会得到字符串首字符索引'0')。 - BOOLEAN + INSTANCE_SWAP 组合:
'Show Icon'是布尔开关,'Icon'是实例交换槽位。iconInstance.componentPropertyReferences同时挂visible: showIconKey(控制可见性)和mainComponent: iconSlotKey(控制可替换的主组件),这是"图标槽位 + 显隐开关"的标准写法。comp.insertChild(0, iconInstance)保证图标排在标签之前。 combineAsVariants(components, figma.currentPage)把独立组件合并为 COMPONENT_SET(类型定义见 plugin-api-standalone.d.ts#L1742-L1749),之后componentSet.name = "Button"重命名组件集。- 合并后变体全部堆在 (0,0):必须手动按
i * colW排开;并用resizeWithoutConstraints依据实际子节点包围盒外扩 40px 重设尺寸——原文档特意注明"formula-based sizing is error-prone"(用公式推算组件集尺寸容易出错,应以真实子节点边界为准)。
按 Key 导入团队库组件
// Import a single published component by key
const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY")
const instance = comp.createInstance()
instance.x = 40
instance.y = 40
figma.currentPage.appendChild(instance)
// Import a published component set by key and select a variant
const compSet = await figma.importComponentSetByKeyAsync("COMPONENT_SET_KEY")
const variant =
compSet.children.find((c) =>
c.type === "COMPONENT" && c.name.includes("size=md")
) || compSet.defaultVariant
const variantInstance = variant.createInstance()
variantInstance.x = 240
variantInstance.y = 40
figma.currentPage.appendChild(variantInstance)
return {
componentId: comp.id,
componentSetId: compSet.id,
placedInstanceIds: [instance.id, variantInstance.id]
}
原文档在此节明确划了一条边界:importComponentByKeyAsync 与 importComponentSetByKeyAsync 导入的是团队库(team libraries)中已发布的组件,而不是当前文件里的组件;当前文件内的组件应直接用 figma.getNodeByIdAsync() 或 findOne()/findAll() 定位。变体选择用"按 size=md 命名查找 + compSet.defaultVariant 兜底"的双保险写法,再对选中的变体调用 createInstance()。返回值同时登记组件 ID 与实例 ID(placedInstanceIds),符合"返回所有创建/修改节点 ID"的硬规则。
组件集 + 变量模式的完整模式
这个示例把"变体轴"与"变量模式"对齐:primary/secondary 两个变体分别锁定变量集合中对应的模式。
await figma.loadFontAsync({ family: "Inter", style: "Medium" })
// 1. Create color collection with modes per variant
const colors = figma.variables.createVariableCollection("Component/Colors")
colors.renameMode(colors.modes[0].modeId, "primary")
const primaryMode = colors.modes[0].modeId
const secondaryMode = colors.addMode("secondary")
const bgVar = figma.variables.createVariable("bg", colors, "COLOR")
bgVar.setValueForMode(primaryMode, { r: 0, g: 0.4, b: 0.9, a: 1 })
bgVar.setValueForMode(secondaryMode, { r: 0, g: 0, b: 0, a: 0 })
const textVar = figma.variables.createVariable("text-color", colors, "COLOR")
textVar.setValueForMode(primaryMode, { r: 1, g: 1, b: 1, a: 1 })
textVar.setValueForMode(secondaryMode, { r: 0.1, g: 0.1, b: 0.1, a: 1 })
// 2. Create components with variable bindings
const modeMap = { primary: primaryMode, secondary: secondaryMode }
const components = []
for (const [variantName, modeId] of Object.entries(modeMap)) {
const comp = figma.createComponent()
comp.name = "variant=" + variantName
comp.layoutMode = "HORIZONTAL"
comp.primaryAxisAlignItems = "CENTER"
comp.counterAxisAlignItems = "CENTER"
comp.paddingLeft = 12; comp.paddingRight = 12
comp.layoutSizingHorizontal = "HUG"
comp.layoutSizingVertical = "HUG"
comp.cornerRadius = 6
// Bind background fill to variable
const bgPaint = figma.variables.setBoundVariableForPaint(
{ type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", bgVar
)
comp.fills = [bgPaint]
// Add text with bound color
const label = figma.createText()
label.fontName = { family: "Inter", style: "Medium" }
label.characters = "Button"
label.fontSize = 14
const textPaint = figma.variables.setBoundVariableForPaint(
{ type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", textVar
)
label.fills = [textPaint]
comp.appendChild(label)
// 3. CRITICAL: Set explicit mode so this variant renders correctly
comp.setExplicitVariableModeForCollection(colors, modeId)
components.push(comp)
}
// 4. Combine into component set
const componentSet = figma.combineAsVariants(components, figma.currentPage)
componentSet.name = "Button"
return {
componentSetId: componentSet.id,
colorCollectionId: colors.id
}
四步结构与关键细节:
- 变量集合的模式与变体一一对应:
primary模式背景为蓝色{r:0, g:0.4, b:0.9}、文字白色;secondary模式背景透明{r:0, g:0, b:0, a:0}、文字深灰。注意a字段在变量值层面是允许的(与纯色 paint 的color不带a、透明度放在 paint 层的opacity上不同)。 - 每个组件内完成绑定:背景
comp.fills和文字label.fills都用setBoundVariableForPaint生成新 paint 后整体赋值,前文"捕获返回值"规则再次出现。文本节点在设置characters/fontSize前先label.fontName = { family: "Inter", style: "Medium" },与脚本开头的loadFontAsync呼应。 - 最关键的一步
comp.setExplicitVariableModeForCollection(colors, modeId):原文档以 "CRITICAL" 标注——不显式锁定模式,该变体渲染时可能取到错误模式下的变量值,导致 primary 变体显示出 secondary 的配色。 - 合并与命名:
combineAsVariants+componentSet.name = "Button",return 组件集 ID 和颜色集合 ID 两条线索,供下一轮调用(如排版、截图验证)使用。
大型组件集:多步拆分模式(Multi-Step)
对于 50+ 变体的组件集,原文档给出策略:拆成多次 use_figma 调用,每次只做一件事,用 return 的 ID 作为下一轮的输入字面量。这直接呼应 SKILL.md 中"Incremental Workflow"一节"每次调用最多 10 个逻辑操作、每步验证后再前进"的原则。
第 1 次调用:建变量集合,返回全部 ID
// Hex-to-0-1 helper
const hex = (h) => {
if (!h) return { r: 0, g: 0, b: 0, a: 0 }; // transparent
return {
r: parseInt(h.slice(1,3), 16) / 255,
g: parseInt(h.slice(3,5), 16) / 255,
b: parseInt(h.slice(5,7), 16) / 255,
a: 1
};
};
const coll = figma.variables.createVariableCollection("MyComponent/Colors");
coll.renameMode(coll.modes[0].modeId, "mode1");
const mode2Id = coll.addMode("mode2");
// Create variables from data map
const colorData = { "bg/default": ["#0B6BCB", "#636B74"], /* ... */ };
const modeOrder = ["mode1", "mode2"];
const modeIds = { mode1: coll.modes[0].modeId, mode2: mode2Id };
const varIds = {};
for (const [name, values] of Object.entries(colorData)) {
const v = figma.variables.createVariable(name, coll, "COLOR");
values.forEach((hex_val, i) => {
v.setValueForMode(modeIds[modeOrder[i]], hex_val ? hex(hex_val) : { r:0, g:0, b:0, a:0 });
});
varIds[name] = v.id;
}
// Return ALL IDs — needed by subsequent calls
return { collId: coll.id, modeIds, varIds };
工程化要点:
hex辅助函数:把#RRGGBB十六进制拆成三段parseInt(x, 16) / 255归一到 0–1;空值返回全透明{r:0, g:0, b:0, a:0},让数据表可以直接表达"该模式无此颜色"。- 数据驱动:颜色定义集中在
colorData映射(键如"bg/default"编码了"用途/状态"),循环统一创建变量并逐模式赋值,避免为每个变量手写一段setValueForMode。 - return
{ collId, modeIds, varIds }:集合 ID、模式 ID 表、变量 ID 表全部交回,这是第 2 次调用的全部依赖。
第 2 次调用:用存储的 ID 建组件、合并、排版
await figma.loadFontAsync({ family: "Inter", style: "Semi Bold" });
// Paste IDs from Call 1 as literals
const collId = "VariableCollectionId:X:Y";
const modeIds = { mode1: "X:0", mode2: "X:1" };
const varIds = { /* ... from Call 1 ... */ };
const getVar = async (id) => await figma.variables.getVariableByIdAsync(id);
const bindColor = async (varId) => figma.variables.setBoundVariableForPaint(
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', await getVar(varId)
);
const collection = await figma.variables.getVariableCollectionByIdAsync(collId);
const components = [];
for (const mode of ["mode1", "mode2"]) {
for (const state of ["default", "hover"]) {
const comp = figma.createComponent();
comp.name = `mode=${mode}, state=${state}`;
comp.layoutMode = 'HORIZONTAL';
comp.primaryAxisAlignItems = 'CENTER';
comp.counterAxisAlignItems = 'CENTER';
comp.layoutSizingHorizontal = 'HUG';
comp.layoutSizingVertical = 'HUG';
comp.fills = [await bindColor(varIds[`bg/${state}`])];
comp.setExplicitVariableModeForCollection(collection, modeIds[mode]);
// ... add text children ...
components.push(comp);
}
}
// Combine — all children stack at (0,0)!
const cs = figma.combineAsVariants(components, figma.currentPage);
cs.name = "MyComponent";
// CRITICAL: layout variants in a structured grid mapped to variant axes.
const stateOrder = ["default", "hover"];
const modeOrder2 = ["mode1", "mode2"];
const colW = 140, rowH = 56;
for (const child of cs.children) {
const props = Object.fromEntries(
child.name.split(', ').map(p => p.split('='))
);
const col = stateOrder.indexOf(props.state);
const row = modeOrder2.indexOf(props.mode);
child.x = col * colW;
child.y = row * rowH;
}
// Resize from actual child bounds
let maxX = 0, maxY = 0;
for (const child of cs.children) {
maxX = Math.max(maxX, child.x + child.width);
maxY = Math.max(maxY, child.y + child.height);
}
cs.resizeWithoutConstraints(maxX + 40, maxY + 40);
// Wrap in section
const section = figma.createSection();
section.name = "MyComponent Section";
section.appendChild(cs);
section.resize(cs.width + 200, cs.height + 200);
return { csId: cs.id, count: components.length };
这段是全文档信息密度最高的示例,值得逐段对照:
- ID 以字符串字面量粘贴:注释 "Paste IDs from Call 1 as literals" 点破了多步调用的数据传递方式——跨调用没有共享变量,第 1 次 return 的 JSON 由调用方原样嵌入第 2 次脚本。SKILL.md 的 Pre-Flight Checklist 中也有对应条目:"IDs from previous calls are passed as string literals (not variables)"。
- 两个小工具函数收拢样板:
getVar封装getVariableByIdAsync,bindColor把"取变量 + 生成绑定 paint"压缩成一步,comp.fills = [await bindColor(...)]即可。 - 双轴变体命名:
mode=${mode}, state=${state}声明了mode与state两个属性轴;后续排版代码用child.name.split(', ').map(p => p.split('='))+Object.fromEntries把名字反解析成属性表,再按stateOrder/modeOrder2查行列索引,把变体摆进"state 为列、mode 为行"的网格(child.x = col * colW; child.y = row * rowH)。这比单行排列更适合多变体集,也让网格对人类和后续脚本都可读。 - 再次出现的三条纪律:合并后子节点全在 (0,0) 需要手动排版;尺寸用
resizeWithoutConstraints从实际包围盒外扩得出;setExplicitVariableModeForCollection在每个组件上逐一调用。 - 用 Section 包裹:
figma.createSection()建分区并appendChild(cs)后按+200外扩 resize,给组件集留出画布余量,这是交付前的组织动作。
读取既有节点:只读巡检脚本
最后一个示例展示了"只读调用"——不创建任何节点,只把结构数据 return 回去:
const page = figma.currentPage
const nodes = page.findAll(n => n.type === 'FRAME')
const data = nodes.map(n => ({
id: n.id,
name: n.name,
width: n.width,
height: n.height,
childCount: n.children?.length || 0
}))
return { frames: data }
它的价值在于配合 SKILL.md 增量工作流的第 1 步 Inspect first:写任何创建脚本前,先跑一轮只读脚本摸清文件里已有哪些 frame、命名习惯和层级,让新内容"匹配现状而不是强加新约定"。同一技能中还提供了列出全部页面/组件/变量集合的巡检脚本,可作为扩展模板。
模式速查:何时用哪段骨架
| 场景 | 核心 API | 对应章节 |
|---|---|---|
| 任何脚本收尾 | 结构化 return { createdNodeIds, mutatedNodeIds, ... } |
基本脚本结构 |
| 新建顶层节点 | 空位扫描 maxX + 100 定位 |
形状/文本/Frame 示例 |
| 文本 | 先 loadFontAsync,后 createText |
文本节点 |
| 容器 | figma.createAutoLayout('VERTICAL') |
自动布局 Frame |
| 主题变量 | createVariableCollection + renameMode/addMode + setValueForMode |
变量集合 |
| 变量上色 | setBoundVariableForPaint 返回值再赋 fills |
填充绑定 |
| 变体属性 | 循环内 addComponentProperty,再 combineAsVariants |
组件变体 |
| 团队库复用 | importComponentByKeyAsync / importComponentSetByKeyAsync |
按 Key 导入 |
| 变体 × 模式 | 每组件 setExplicitVariableModeForCollection |
变量模式组件集 |
| 50+ 变体 | 调用 1 建变量 return ID,调用 2 粘贴字面量建组件 | 多步模式 |
| 写前侦察 | page.findAll + return 结构数据 |
读取节点 |
需要进一步深入时,建议按 SKILL.md 第 10 节的参考文档索引继续加载:plugin-api-patterns.md(fills、strokes、effects 等节点细节)、variable-patterns.md(scopes 与别名)、component-patterns.md(INSTANCE_SWAP 与变体排版)、text-style-patterns.md(字体发现与文字样式)、gotchas.md(全部已知坑位的 WRONG/CORRECT 对照),以及作为 API 权威来源的 plugin-api-standalone.d.ts(建议按符号 grep 而不是整文件加载)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00