figma

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Figma API Integration

Figma API 集成

Extract design data, generate code from components, and automate design workflows with Figma's API.
借助Figma的API提取设计数据、从组件生成代码并自动化设计工作流。

Quick Start

快速开始

Authentication

身份验证

typescript
const FIGMA_TOKEN = process.env.FIGMA_TOKEN;

const headers = {
  'X-Figma-Token': FIGMA_TOKEN
};

// Get file
const response = await fetch(
  `https://api.figma.com/v1/files/${FILE_KEY}`,
  { headers }
);
typescript
const FIGMA_TOKEN = process.env.FIGMA_TOKEN;

const headers = {
  'X-Figma-Token': FIGMA_TOKEN
};

// Get file
const response = await fetch(
  `https://api.figma.com/v1/files/${FILE_KEY}`,
  { headers }
);

File Key & Node IDs

文件密钥与节点ID

typescript
// Extract from Figma URL: figma.com/file/FILE_KEY/Name?node-id=NODE_ID
const figmaUrl = 'https://www.figma.com/file/abc123/MyDesign?node-id=1%3A2';
const fileKey = figmaUrl.match(/file\/([^/]+)/)?.[1];  // abc123
const nodeId = new URL(figmaUrl).searchParams.get('node-id');  // 1:2
typescript
// Extract from Figma URL: figma.com/file/FILE_KEY/Name?node-id=NODE_ID
const figmaUrl = 'https://www.figma.com/file/abc123/MyDesign?node-id=1%3A2';
const fileKey = figmaUrl.match(/file\/([^/]+)/)?.[1];  // abc123
const nodeId = new URL(figmaUrl).searchParams.get('node-id');  // 1:2

Core API Endpoints

核心API端点

Get File

获取文件

typescript
// Full file
GET https://api.figma.com/v1/files/:file_key

// Specific nodes (components)
GET https://api.figma.com/v1/files/:file_key/nodes?ids=1:2,1:3

// With geometry for SVG paths
GET https://api.figma.com/v1/files/:file_key?geometry=paths

// With plugin data
GET https://api.figma.com/v1/files/:file_key?plugin_data=shared
typescript
// Full file
GET https://api.figma.com/v1/files/:file_key

// Specific nodes (components)
GET https://api.figma.com/v1/files/:file_key/nodes?ids=1:2,1:3

// With geometry for SVG paths
GET https://api.figma.com/v1/files/:file_key?geometry=paths

// With plugin data
GET https://api.figma.com/v1/files/:file_key?plugin_data=shared

Get Components

获取组件

typescript
// Get all components in a file
GET https://api.figma.com/v1/files/:file_key/components

// Get component sets (variants)
GET https://api.figma.com/v1/files/:file_key/component_sets

// Get team's published components
GET https://api.figma.com/v1/teams/:team_id/components
typescript
// Get all components in a file
GET https://api.figma.com/v1/files/:file_key/components

// Get component sets (variants)
GET https://api.figma.com/v1/files/:file_key/component_sets

// Get team's published components
GET https://api.figma.com/v1/teams/:team_id/components

Export Images

导出图片

typescript
// Export nodes as images
GET https://api.figma.com/v1/images/:file_key?ids=1:2,1:3&format=png&scale=2

// Export as SVG
GET https://api.figma.com/v1/images/:file_key?ids=1:2&format=svg

// Response
{
  "images": {
    "1:2": "https://s3.amazonaws.com/...",
    "1:3": "https://s3.amazonaws.com/..."
  }
}
typescript
// Export nodes as images
GET https://api.figma.com/v1/images/:file_key?ids=1:2,1:3&format=png&scale=2

// Export as SVG
GET https://api.figma.com/v1/images/:file_key?ids=1:2&format=svg

// Response
{
  "images": {
    "1:2": "https://s3.amazonaws.com/...",
    "1:3": "https://s3.amazonaws.com/..."
  }
}

Component Code Generation

组件代码生成

Figma Node to React Component

Figma节点转React组件

typescript
interface FigmaNode {
  id: string;
  name: string;
  type: string;
  children?: FigmaNode[];
  absoluteBoundingBox?: { x: number; y: number; width: number; height: number };
  fills?: Fill[];
  strokes?: Stroke[];
  effects?: Effect[];
  cornerRadius?: number;
  paddingLeft?: number;
  paddingRight?: number;
  paddingTop?: number;
  paddingBottom?: number;
  itemSpacing?: number;
  layoutMode?: 'HORIZONTAL' | 'VERTICAL' | 'NONE';
  primaryAxisAlignItems?: string;
  counterAxisAlignItems?: string;
  characters?: string;
  style?: TextStyle;
}

async function getComponentCode(fileKey: string, nodeId: string): Promise<string> {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${nodeId}`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  const data = await response.json();
  const node = data.nodes[nodeId].document;
  
  return generateReactComponent(node);
}

function generateReactComponent(node: FigmaNode): string {
  const componentName = toPascalCase(node.name);
  const styles = extractStyles(node);
  const children = node.children?.map(child => generateJSX(child)).join('\n') || '';
  
  return `
import React from 'react';

export function ${componentName}() {
  return (
    <div style={${JSON.stringify(styles, null, 2)}}>
      ${children}
    </div>
  );
}
`;
}

function extractStyles(node: FigmaNode): React.CSSProperties {
  const styles: React.CSSProperties = {};
  
  // Dimensions
  if (node.absoluteBoundingBox) {
    styles.width = node.absoluteBoundingBox.width;
    styles.height = node.absoluteBoundingBox.height;
  }
  
  // Background
  if (node.fills?.length) {
    const fill = node.fills.find(f => f.visible !== false && f.type === 'SOLID');
    if (fill?.color) {
      styles.backgroundColor = rgbaToHex(fill.color);
    }
  }
  
  // Border radius
  if (node.cornerRadius) {
    styles.borderRadius = node.cornerRadius;
  }
  
  // Padding
  if (node.paddingLeft) styles.paddingLeft = node.paddingLeft;
  if (node.paddingRight) styles.paddingRight = node.paddingRight;
  if (node.paddingTop) styles.paddingTop = node.paddingTop;
  if (node.paddingBottom) styles.paddingBottom = node.paddingBottom;
  
  // Flexbox (Auto Layout)
  if (node.layoutMode && node.layoutMode !== 'NONE') {
    styles.display = 'flex';
    styles.flexDirection = node.layoutMode === 'HORIZONTAL' ? 'row' : 'column';
    styles.gap = node.itemSpacing;
    
    // Alignment
    const alignMap: Record<string, string> = {
      'MIN': 'flex-start',
      'CENTER': 'center',
      'MAX': 'flex-end',
      'SPACE_BETWEEN': 'space-between',
    };
    if (node.primaryAxisAlignItems) {
      styles.justifyContent = alignMap[node.primaryAxisAlignItems] || 'flex-start';
    }
    if (node.counterAxisAlignItems) {
      styles.alignItems = alignMap[node.counterAxisAlignItems] || 'flex-start';
    }
  }
  
  return styles;
}

function generateJSX(node: FigmaNode): string {
  const styles = extractStyles(node);
  
  if (node.type === 'TEXT') {
    return `<span style={${JSON.stringify(styles)}}>${node.characters || ''}</span>`;
  }
  
  if (node.type === 'VECTOR' || node.type === 'BOOLEAN_OPERATION') {
    return `{/* Vector: ${node.name} - export as SVG */}`;
  }
  
  const children = node.children?.map(child => generateJSX(child)).join('\n') || '';
  return `<div style={${JSON.stringify(styles)}}>${children}</div>`;
}
typescript
interface FigmaNode {
  id: string;
  name: string;
  type: string;
  children?: FigmaNode[];
  absoluteBoundingBox?: { x: number; y: number; width: number; height: number };
  fills?: Fill[];
  strokes?: Stroke[];
  effects?: Effect[];
  cornerRadius?: number;
  paddingLeft?: number;
  paddingRight?: number;
  paddingTop?: number;
  paddingBottom?: number;
  itemSpacing?: number;
  layoutMode?: 'HORIZONTAL' | 'VERTICAL' | 'NONE';
  primaryAxisAlignItems?: string;
  counterAxisAlignItems?: string;
  characters?: string;
  style?: TextStyle;
}

async function getComponentCode(fileKey: string, nodeId: string): Promise<string> {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${nodeId}`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  const data = await response.json();
  const node = data.nodes[nodeId].document;
  
  return generateReactComponent(node);
}

function generateReactComponent(node: FigmaNode): string {
  const componentName = toPascalCase(node.name);
  const styles = extractStyles(node);
  const children = node.children?.map(child => generateJSX(child)).join('\n') || '';
  
  return `
import React from 'react';

export function ${componentName}() {
  return (
    <div style={${JSON.stringify(styles, null, 2)}}>
      ${children}
    </div>
  );
}
`;
}

function extractStyles(node: FigmaNode): React.CSSProperties {
  const styles: React.CSSProperties = {};
  
  // Dimensions
  if (node.absoluteBoundingBox) {
    styles.width = node.absoluteBoundingBox.width;
    styles.height = node.absoluteBoundingBox.height;
  }
  
  // Background
  if (node.fills?.length) {
    const fill = node.fills.find(f => f.visible !== false && f.type === 'SOLID');
    if (fill?.color) {
      styles.backgroundColor = rgbaToHex(fill.color);
    }
  }
  
  // Border radius
  if (node.cornerRadius) {
    styles.borderRadius = node.cornerRadius;
  }
  
  // Padding
  if (node.paddingLeft) styles.paddingLeft = node.paddingLeft;
  if (node.paddingRight) styles.paddingRight = node.paddingRight;
  if (node.paddingTop) styles.paddingTop = node.paddingTop;
  if (node.paddingBottom) styles.paddingBottom = node.paddingBottom;
  
  // Flexbox (Auto Layout)
  if (node.layoutMode && node.layoutMode !== 'NONE') {
    styles.display = 'flex';
    styles.flexDirection = node.layoutMode === 'HORIZONTAL' ? 'row' : 'column';
    styles.gap = node.itemSpacing;
    
    // Alignment
    const alignMap: Record<string, string> = {
      'MIN': 'flex-start',
      'CENTER': 'center',
      'MAX': 'flex-end',
      'SPACE_BETWEEN': 'space-between',
    };
    if (node.primaryAxisAlignItems) {
      styles.justifyContent = alignMap[node.primaryAxisAlignItems] || 'flex-start';
    }
    if (node.counterAxisAlignItems) {
      styles.alignItems = alignMap[node.counterAxisAlignItems] || 'flex-start';
    }
  }
  
  return styles;
}

function generateJSX(node: FigmaNode): string {
  const styles = extractStyles(node);
  
  if (node.type === 'TEXT') {
    return `<span style={${JSON.stringify(styles)}}>${node.characters || ''}</span>`;
  }
  
  if (node.type === 'VECTOR' || node.type === 'BOOLEAN_OPERATION') {
    return `{/* Vector: ${node.name} - export as SVG */}`;
  }
  
  const children = node.children?.map(child => generateJSX(child)).join('\n') || '';
  return `<div style={${JSON.stringify(styles)}}>${children}</div>`;
}

Generate Tailwind CSS from Figma

从Figma生成Tailwind CSS

typescript
function figmaToTailwind(node: FigmaNode): string {
  const classes: string[] = [];
  
  // Dimensions
  if (node.absoluteBoundingBox) {
    const { width, height } = node.absoluteBoundingBox;
    classes.push(`w-[${Math.round(width)}px]`, `h-[${Math.round(height)}px]`);
  }
  
  // Background color
  if (node.fills?.length) {
    const fill = node.fills.find(f => f.visible !== false && f.type === 'SOLID');
    if (fill?.color) {
      const hex = rgbaToHex(fill.color);
      classes.push(`bg-[${hex}]`);
    }
  }
  
  // Border radius
  if (node.cornerRadius) {
    const radiusMap: Record<number, string> = {
      4: 'rounded-sm', 6: 'rounded-md', 8: 'rounded-lg',
      12: 'rounded-xl', 16: 'rounded-2xl', 9999: 'rounded-full'
    };
    classes.push(radiusMap[node.cornerRadius] || `rounded-[${node.cornerRadius}px]`);
  }
  
  // Padding
  if (node.paddingLeft === node.paddingRight && 
      node.paddingTop === node.paddingBottom &&
      node.paddingLeft === node.paddingTop) {
    classes.push(`p-[${node.paddingLeft}px]`);
  } else {
    if (node.paddingLeft) classes.push(`pl-[${node.paddingLeft}px]`);
    if (node.paddingRight) classes.push(`pr-[${node.paddingRight}px]`);
    if (node.paddingTop) classes.push(`pt-[${node.paddingTop}px]`);
    if (node.paddingBottom) classes.push(`pb-[${node.paddingBottom}px]`);
  }
  
  // Flexbox
  if (node.layoutMode && node.layoutMode !== 'NONE') {
    classes.push('flex');
    classes.push(node.layoutMode === 'HORIZONTAL' ? 'flex-row' : 'flex-col');
    if (node.itemSpacing) classes.push(`gap-[${node.itemSpacing}px]`);
    
    const justifyMap: Record<string, string> = {
      'MIN': 'justify-start', 'CENTER': 'justify-center',
      'MAX': 'justify-end', 'SPACE_BETWEEN': 'justify-between'
    };
    const alignMap: Record<string, string> = {
      'MIN': 'items-start', 'CENTER': 'items-center', 'MAX': 'items-end'
    };
    if (node.primaryAxisAlignItems) classes.push(justifyMap[node.primaryAxisAlignItems]);
    if (node.counterAxisAlignItems) classes.push(alignMap[node.counterAxisAlignItems]);
  }
  
  return classes.join(' ');
}
typescript
function figmaToTailwind(node: FigmaNode): string {
  const classes: string[] = [];
  
  // Dimensions
  if (node.absoluteBoundingBox) {
    const { width, height } = node.absoluteBoundingBox;
    classes.push(`w-[${Math.round(width)}px]`, `h-[${Math.round(height)}px]`);
  }
  
  // Background color
  if (node.fills?.length) {
    const fill = node.fills.find(f => f.visible !== false && f.type === 'SOLID');
    if (fill?.color) {
      const hex = rgbaToHex(fill.color);
      classes.push(`bg-[${hex}]`);
    }
  }
  
  // Border radius
  if (node.cornerRadius) {
    const radiusMap: Record<number, string> = {
      4: 'rounded-sm', 6: 'rounded-md', 8: 'rounded-lg',
      12: 'rounded-xl', 16: 'rounded-2xl', 9999: 'rounded-full'
    };
    classes.push(radiusMap[node.cornerRadius] || `rounded-[${node.cornerRadius}px]`);
  }
  
  // Padding
  if (node.paddingLeft === node.paddingRight && 
      node.paddingTop === node.paddingBottom &&
      node.paddingLeft === node.paddingTop) {
    classes.push(`p-[${node.paddingLeft}px]`);
  } else {
    if (node.paddingLeft) classes.push(`pl-[${node.paddingLeft}px]`);
    if (node.paddingRight) classes.push(`pr-[${node.paddingRight}px]`);
    if (node.paddingTop) classes.push(`pt-[${node.paddingTop}px]`);
    if (node.paddingBottom) classes.push(`pb-[${node.paddingBottom}px]`);
  }
  
  // Flexbox
  if (node.layoutMode && node.layoutMode !== 'NONE') {
    classes.push('flex');
    classes.push(node.layoutMode === 'HORIZONTAL' ? 'flex-row' : 'flex-col');
    if (node.itemSpacing) classes.push(`gap-[${node.itemSpacing}px]`);
    
    const justifyMap: Record<string, string> = {
      'MIN': 'justify-start', 'CENTER': 'justify-center',
      'MAX': 'justify-end', 'SPACE_BETWEEN': 'justify-between'
    };
    const alignMap: Record<string, string> = {
      'MIN': 'items-start', 'CENTER': 'items-center', 'MAX': 'items-end'
    };
    if (node.primaryAxisAlignItems) classes.push(justifyMap[node.primaryAxisAlignItems]);
    if (node.counterAxisAlignItems) classes.push(alignMap[node.counterAxisAlignItems]);
  }
  
  return classes.join(' ');
}

Extract All Components from File

提取文件中所有组件

typescript
interface ComponentInfo {
  key: string;
  name: string;
  description: string;
  nodeId: string;
  thumbnailUrl?: string;
}

async function getAllComponents(fileKey: string): Promise<ComponentInfo[]> {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/components`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  const data = await response.json();
  
  return data.meta.components.map((comp: any) => ({
    key: comp.key,
    name: comp.name,
    description: comp.description || '',
    nodeId: comp.node_id,
    thumbnailUrl: comp.thumbnail_url,
  }));
}

// Generate code for all components
async function generateAllComponentsCode(fileKey: string): Promise<Map<string, string>> {
  const components = await getAllComponents(fileKey);
  const codeMap = new Map<string, string>();
  
  for (const comp of components) {
    const code = await getComponentCode(fileKey, comp.nodeId);
    codeMap.set(comp.name, code);
  }
  
  return codeMap;
}
typescript
interface ComponentInfo {
  key: string;
  name: string;
  description: string;
  nodeId: string;
  thumbnailUrl?: string;
}

async function getAllComponents(fileKey: string): Promise<ComponentInfo[]> {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/components`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  const data = await response.json();
  
  return data.meta.components.map((comp: any) => ({
    key: comp.key,
    name: comp.name,
    description: comp.description || '',
    nodeId: comp.node_id,
    thumbnailUrl: comp.thumbnail_url,
  }));
}

// 为所有组件生成代码
async function generateAllComponentsCode(fileKey: string): Promise<Map<string, string>> {
  const components = await getAllComponents(fileKey);
  const codeMap = new Map<string, string>();
  
  for (const comp of components) {
    const code = await getComponentCode(fileKey, comp.nodeId);
    codeMap.set(comp.name, code);
  }
  
  return codeMap;
}

Design Token Extraction

设计令牌提取

Extract Colors

提取颜色

typescript
async function extractColors(fileKey: string) {
  const file = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/styles`,
    { headers }
  ).then(r => r.json());
  
  const colors = {};
  
  for (const style of file.meta.styles) {
    if (style.style_type === 'FILL') {
      const nodeData = await getStyleNode(fileKey, style.node_id);
      const fill = nodeData.fills[0];
      
      if (fill.type === 'SOLID') {
        colors[style.name] = rgbaToHex(fill.color);
      }
    }
  }
  
  return colors;
}

function rgbaToHex({ r, g, b, a = 1 }) {
  const toHex = (n) => Math.round(n * 255).toString(16).padStart(2, '0');
  return `#${toHex(r)}${toHex(g)}${toHex(b)}${a < 1 ? toHex(a) : ''}`;
}
typescript
async function extractColors(fileKey: string) {
  const file = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/styles`,
    { headers }
  ).then(r => r.json());
  
  const colors = {};
  
  for (const style of file.meta.styles) {
    if (style.style_type === 'FILL') {
      const nodeData = await getStyleNode(fileKey, style.node_id);
      const fill = nodeData.fills[0];
      
      if (fill.type === 'SOLID') {
        colors[style.name] = rgbaToHex(fill.color);
      }
    }
  }
  
  return colors;
}

function rgbaToHex({ r, g, b, a = 1 }) {
  const toHex = (n) => Math.round(n * 255).toString(16).padStart(2, '0');
  return `#${toHex(r)}${toHex(g)}${toHex(b)}${a < 1 ? toHex(a) : ''}`;
}

Extract Typography Tokens

提取排版令牌

typescript
async function extractTypography(fileKey: string) {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/styles`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  const data = await response.json();
  
  const typography: Record<string, any> = {};
  
  for (const style of data.meta.styles) {
    if (style.style_type === 'TEXT') {
      const nodeResponse = await fetch(
        `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${style.node_id}`,
        { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
      );
      const nodeData = await nodeResponse.json();
      const node = nodeData.nodes[style.node_id].document;
      
      typography[style.name] = {
        fontFamily: node.style?.fontFamily,
        fontWeight: node.style?.fontWeight,
        fontSize: node.style?.fontSize,
        lineHeight: node.style?.lineHeightPx,
        letterSpacing: node.style?.letterSpacing,
      };
    }
  }
  
  return typography;
}
typescript
async function extractTypography(fileKey: string) {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/styles`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  const data = await response.json();
  
  const typography: Record<string, any> = {};
  
  for (const style of data.meta.styles) {
    if (style.style_type === 'TEXT') {
      const nodeResponse = await fetch(
        `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${style.node_id}`,
        { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
      );
      const nodeData = await nodeResponse.json();
      const node = nodeData.nodes[style.node_id].document;
      
      typography[style.name] = {
        fontFamily: node.style?.fontFamily,
        fontWeight: node.style?.fontWeight,
        fontSize: node.style?.fontSize,
        lineHeight: node.style?.lineHeightPx,
        letterSpacing: node.style?.letterSpacing,
      };
    }
  }
  
  return typography;
}

Generate Complete Design Tokens

生成完整设计令牌

typescript
interface DesignTokens {
  colors: Record<string, string>;
  typography: Record<string, any>;
  spacing: Record<string, number>;
  shadows: Record<string, string>;
  radii: Record<string, number>;
}

async function generateDesignTokens(fileKey: string): Promise<DesignTokens> {
  const [colors, typography] = await Promise.all([
    extractColors(fileKey),
    extractTypography(fileKey),
  ]);
  
  return {
    colors,
    typography,
    spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },  // Extract from file
    shadows: {},  // Extract from effect styles
    radii: {},    // Extract from nodes
  };
}

// Generate CSS Variables
function tokensToCSSVariables(tokens: DesignTokens): string {
  let css = ':root {\n';
  
  // Colors
  for (const [name, value] of Object.entries(tokens.colors)) {
    const cssName = name.toLowerCase().replace(/[\s/]+/g, '-');
    css += `  --color-${cssName}: ${value};\n`;
  }
  
  // Typography
  for (const [name, style] of Object.entries(tokens.typography)) {
    const cssName = name.toLowerCase().replace(/[\s/]+/g, '-');
    css += `  --font-${cssName}-family: "${style.fontFamily}";\n`;
    css += `  --font-${cssName}-size: ${style.fontSize}px;\n`;
    css += `  --font-${cssName}-weight: ${style.fontWeight};\n`;
    css += `  --font-${cssName}-line-height: ${style.lineHeight}px;\n`;
  }
  
  css += '}\n';
  return css;
}

// Generate Tailwind Config
function tokensToTailwindConfig(tokens: DesignTokens): string {
  const colors: Record<string, string> = {};
  for (const [name, value] of Object.entries(tokens.colors)) {
    const key = name.toLowerCase().replace(/[\s/]+/g, '-');
    colors[key] = value;
  }
  
  return `
module.exports = {
  theme: {
    extend: {
      colors: ${JSON.stringify(colors, null, 6)},
      fontFamily: {
        ${Object.entries(tokens.typography).map(([name, style]) => 
          `'${name.toLowerCase()}': ['${style.fontFamily}', 'sans-serif']`
        ).join(',\n        ')}
      },
    },
  },
};
`;
}
typescript
interface DesignTokens {
  colors: Record<string, string>;
  typography: Record<string, any>;
  spacing: Record<string, number>;
  shadows: Record<string, string>;
  radii: Record<string, number>;
}

async function generateDesignTokens(fileKey: string): Promise<DesignTokens> {
  const [colors, typography] = await Promise.all([
    extractColors(fileKey),
    extractTypography(fileKey),
  ]);
  
  return {
    colors,
    typography,
    spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },  // Extract from file
    shadows: {},  // Extract from effect styles
    radii: {},    // Extract from nodes
  };
}

// 生成CSS变量
function tokensToCSSVariables(tokens: DesignTokens): string {
  let css = ':root {\n';
  
  // Colors
  for (const [name, value] of Object.entries(tokens.colors)) {
    const cssName = name.toLowerCase().replace(/[\s/]+/g, '-');
    css += `  --color-${cssName}: ${value};\n`;
  }
  
  // Typography
  for (const [name, style] of Object.entries(tokens.typography)) {
    const cssName = name.toLowerCase().replace(/[\s/]+/g, '-');
    css += `  --font-${cssName}-family: "${style.fontFamily}";\n`;
    css += `  --font-${cssName}-size: ${style.fontSize}px;\n`;
    css += `  --font-${cssName}-weight: ${style.fontWeight};\n`;
    css += `  --font-${cssName}-line-height: ${style.lineHeight}px;\n`;
  }
  
  css += '}\n';
  return css;
}

// 生成Tailwind配置
function tokensToTailwindConfig(tokens: DesignTokens): string {
  const colors: Record<string, string> = {};
  for (const [name, value] of Object.entries(tokens.colors)) {
    const key = name.toLowerCase().replace(/[\s/]+/g, '-');
    colors[key] = value;
  }
  
  return `
module.exports = {
  theme: {
    extend: {
      colors: ${JSON.stringify(colors, null, 6)},
      fontFamily: {
        ${Object.entries(tokens.typography).map(([name, style]) => 
          `'${name.toLowerCase()}': ['${style.fontFamily}', 'sans-serif']`
        ).join(',\n        ')}
      },
    },
  },
};
`;
}

Variables API (Design Tokens 2.0)

Variables API(设计令牌2.0)

typescript
// Get variables (requires enterprise/organization plan)
GET https://api.figma.com/v1/files/:file_key/variables/local

// Get variable collections
GET https://api.figma.com/v1/files/:file_key/variables/local/collections

// Response structure
interface VariableCollection {
  id: string;
  name: string;
  modes: { modeId: string; name: string }[];
  variableIds: string[];
}

interface Variable {
  id: string;
  name: string;
  resolvedType: 'BOOLEAN' | 'FLOAT' | 'STRING' | 'COLOR';
  valuesByMode: Record<string, any>;
}

async function getVariables(fileKey: string) {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/variables/local`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  return response.json();
}
typescript
// Get variables (requires enterprise/organization plan)
GET https://api.figma.com/v1/files/:file_key/variables/local

// Get variable collections
GET https://api.figma.com/v1/files/:file_key/variables/local/collections

// Response structure
interface VariableCollection {
  id: string;
  name: string;
  modes: { modeId: string; name: string }[];
  variableIds: string[];
}

interface Variable {
  id: string;
  name: string;
  resolvedType: 'BOOLEAN' | 'FLOAT' | 'STRING' | 'COLOR';
  valuesByMode: Record<string, any>;
}

async function getVariables(fileKey: string) {
  const response = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/variables/local`,
    { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
  );
  return response.json();
}

Dev Mode Integration

Dev Mode集成

Code Snippets from Dev Mode

从Dev Mode获取代码片段

typescript
// Get Dev Resources (annotations, measurements)
GET https://api.figma.com/v1/files/:file_key/dev_resources

// Create Dev Resource
POST https://api.figma.com/v1/dev_resources
{
  "dev_resource": {
    "name": "React Component",
    "url": "https://github.com/...",
    "file_key": "abc123",
    "node_id": "1:2"
  }
}
typescript
// Get Dev Resources (annotations, measurements)
GET https://api.figma.com/v1/files/:file_key/dev_resources

// Create Dev Resource
POST https://api.figma.com/v1/dev_resources
{
  "dev_resource": {
    "name": "React Component",
    "url": "https://github.com/...",
    "file_key": "abc123",
    "node_id": "1:2"
  }
}

Webhooks

Webhooks

Setup Webhook

设置Webhook

typescript
POST https://api.figma.com/v2/webhooks

{
  "event_type": "FILE_UPDATE",
  "team_id": "123456",
  "endpoint": "https://your-server.com/figma-webhook",
  "passcode": "your-secret-passcode"
}

// Available events:
// - FILE_UPDATE
// - FILE_DELETE  
// - FILE_VERSION_UPDATE
// - LIBRARY_PUBLISH
// - FILE_COMMENT
typescript
POST https://api.figma.com/v2/webhooks

{
  "event_type": "FILE_UPDATE",
  "team_id": "123456",
  "endpoint": "https://your-server.com/figma-webhook",
  "passcode": "your-secret-passcode"
}

// Available events:
// - FILE_UPDATE
// - FILE_DELETE  
// - FILE_VERSION_UPDATE
// - LIBRARY_PUBLISH
// - FILE_COMMENT

Handle Webhook

处理Webhook

typescript
app.post('/figma-webhook', async (req, res) => {
  const { passcode } = req.body;
  
  if (passcode !== process.env.FIGMA_WEBHOOK_SECRET) {
    return res.status(401).send('Unauthorized');
  }
  
  const { event_type, file_key, timestamp } = req.body;
  
  switch (event_type) {
    case 'FILE_UPDATE':
      await syncDesignTokens(file_key);
      break;
    case 'LIBRARY_PUBLISH':
      await regenerateComponents(file_key);
      break;
    case 'FILE_COMMENT':
      await notifyTeam(req.body);
      break;
  }
  
  res.status(200).send('OK');
});
typescript
app.post('/figma-webhook', async (req, res) => {
  const { passcode } = req.body;
  
  if (passcode !== process.env.FIGMA_WEBHOOK_SECRET) {
    return res.status(401).send('Unauthorized');
  }
  
  const { event_type, file_key, timestamp } = req.body;
  
  switch (event_type) {
    case 'FILE_UPDATE':
      await syncDesignTokens(file_key);
      break;
    case 'LIBRARY_PUBLISH':
      await regenerateComponents(file_key);
      break;
    case 'FILE_COMMENT':
      await notifyTeam(req.body);
      break;
  }
  
  res.status(200).send('OK');
});

Complete Design-to-Code Pipeline

完整设计转代码流水线

typescript
// Full pipeline: Figma file → React components + tokens
async function figmaToCode(fileKey: string, outputDir: string) {
  // 1. Get all components
  const components = await getAllComponents(fileKey);
  
  // 2. Generate design tokens
  const tokens = await generateDesignTokens(fileKey);
  await fs.writeFile(
    `${outputDir}/tokens.css`,
    tokensToCSSVariables(tokens)
  );
  await fs.writeFile(
    `${outputDir}/tailwind.config.js`,
    tokensToTailwindConfig(tokens)
  );
  
  // 3. Generate React components
  for (const comp of components) {
    const code = await getComponentCode(fileKey, comp.nodeId);
    const fileName = toPascalCase(comp.name) + '.tsx';
    await fs.writeFile(`${outputDir}/components/${fileName}`, code);
  }
  
  // 4. Export icons as SVGs
  const icons = components.filter(c => c.name.startsWith('Icon/'));
  if (icons.length) {
    const iconIds = icons.map(i => i.nodeId).join(',');
    const svgResponse = await fetch(
      `https://api.figma.com/v1/images/${fileKey}?ids=${iconIds}&format=svg`,
      { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
    );
    const svgData = await svgResponse.json();
    
    for (const icon of icons) {
      const svgUrl = svgData.images[icon.nodeId];
      const svg = await fetch(svgUrl).then(r => r.text());
      await fs.writeFile(`${outputDir}/icons/${icon.name}.svg`, svg);
    }
  }
  
  console.log(`Generated ${components.length} components and ${Object.keys(tokens.colors).length} color tokens`);
}
typescript
// Full pipeline: Figma file → React components + tokens
async function figmaToCode(fileKey: string, outputDir: string) {
  // 1. Get all components
  const components = await getAllComponents(fileKey);
  
  // 2. Generate design tokens
  const tokens = await generateDesignTokens(fileKey);
  await fs.writeFile(
    `${outputDir}/tokens.css`,
    tokensToCSSVariables(tokens)
  );
  await fs.writeFile(
    `${outputDir}/tailwind.config.js`,
    tokensToTailwindConfig(tokens)
  );
  
  // 3. Generate React components
  for (const comp of components) {
    const code = await getComponentCode(fileKey, comp.nodeId);
    const fileName = toPascalCase(comp.name) + '.tsx';
    await fs.writeFile(`${outputDir}/components/${fileName}`, code);
  }
  
  // 4. Export icons as SVGs
  const icons = components.filter(c => c.name.startsWith('Icon/'));
  if (icons.length) {
    const iconIds = icons.map(i => i.nodeId).join(',');
    const svgResponse = await fetch(
      `https://api.figma.com/v1/images/${fileKey}?ids=${iconIds}&format=svg`,
      { headers: { 'X-Figma-Token': process.env.FIGMA_TOKEN! } }
    );
    const svgData = await svgResponse.json();
    
    for (const icon of icons) {
      const svgUrl = svgData.images[icon.nodeId];
      const svg = await fetch(svgUrl).then(r => r.text());
      await fs.writeFile(`${outputDir}/icons/${icon.name}.svg`, svg);
    }
  }
  
  console.log(`Generated ${components.length} components and ${Object.keys(tokens.colors).length} color tokens`);
}

Figma Plugin Development

Figma插件开发

Plugin Manifest (manifest.json)

插件清单(manifest.json)

json
{
  "name": "My Figma Plugin",
  "id": "123456789",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "ui.html",
  "editorType": ["figma", "figjam"],
  "capabilities": ["codegen"],
  "codegenLanguages": [
    { "label": "React", "value": "react" },
    { "label": "Vue", "value": "vue" }
  ]
}
json
{
  "name": "My Figma Plugin",
  "id": "123456789",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "ui.html",
  "editorType": ["figma", "figjam"],
  "capabilities": ["codegen"],
  "codegenLanguages": [
    { "label": "React", "value": "react" },
    { "label": "Vue", "value": "vue" }
  ]
}

Plugin Code (code.ts)

插件代码(code.ts)

typescript
// Show UI
figma.showUI(__html__, { width: 400, height: 500 });

// Handle selection
figma.on('selectionchange', () => {
  const selection = figma.currentPage.selection;
  figma.ui.postMessage({ type: 'selection', nodes: selection.map(nodeToJSON) });
});

// Handle messages from UI
figma.ui.onmessage = async (msg) => {
  if (msg.type === 'export-code') {
    const node = figma.currentPage.selection[0];
    const code = generateCode(node);
    figma.ui.postMessage({ type: 'code-generated', code });
  }
};

function nodeToJSON(node: SceneNode) {
  return {
    id: node.id,
    name: node.name,
    type: node.type,
    width: node.width,
    height: node.height,
  };
}
typescript
// Show UI
figma.showUI(__html__, { width: 400, height: 500 });

// Handle selection
figma.on('selectionchange', () => {
  const selection = figma.currentPage.selection;
  figma.ui.postMessage({ type: 'selection', nodes: selection.map(nodeToJSON) });
});

// Handle messages from UI
figma.ui.onmessage = async (msg) => {
  if (msg.type === 'export-code') {
    const node = figma.currentPage.selection[0];
    const code = generateCode(node);
    figma.ui.postMessage({ type: 'code-generated', code });
  }
};

function nodeToJSON(node: SceneNode) {
  return {
    id: node.id,
    name: node.name,
    type: node.type,
    width: node.width,
    height: node.height,
  };
}

Resources

资源