#token-parser #figma #tokens #yaml-config #design-tokens #figma-variables

app design_token_parser

Figma 工作室令牌解析器。将您的令牌转换为任何语言的可用代码。

19 个稳定版本

3.2.2 2023年10月31日
3.1.8 2023年9月13日
3.1.7 2023年8月28日
3.1.5 2023年7月10日
1.9.0 2023年6月27日

#41配置

Download history 1/week @ 2024-03-08 75/week @ 2024-03-29 20/week @ 2024-04-05

62 每月下载量

MIT 许可证

135KB
1.5K SLoC

令牌解析器

Crates.io

概述

令牌解析器是一个通用的工具,可以将您的 Figma 令牌转换为任何语言的可运行代码。它是用 Rust 编写的,因此您可以在任何您希望使用的地方使用它,而无需安装除了系统上的可执行文件之外的任何其他东西。完整的配置是通过一个 配置 yaml 文件完成的,您可以从一个地方自定义以构建您想要的任何语言。

测试与

  1. Figma 变量

  2. Figma 工作室令牌

  3. lukasoppermann/design-tokens

只要它们具有正确的 json 结构,您就可以使用来自多个来源的令牌!

设置

您可以从整个项目中获取整个项目并自行构建,或者如果您没有 Rust 或者不想自己处理构建,请转到“发布”部分并从中获取可执行文件。

  1. 设置 assts/design_tokens_config.yaml 文件
  2. 运行:对于 Windows (WIN_design_token_parser.exe) 对于 MAC (MAC_design_token_parser) 您可以在“发布”部分找到它们
   .\WIN_design_token_parser.exe --generate --config "path/design_tokens_config.yaml"

这就完成了,您的文件将生成并准备好使用

生成可用令牌的过程分为两个。

  • 将 Figma 令牌转换为可用的 json 文件(类似于 style-dictionary

  • 从先前生成的 json 文件生成针对特定语言的最终文件

如果您已经生成了可用的 json 文件,只需运行最终代码生成即可。

 WIN_design_token_parser.exe --config "path/design_tokens_config.yaml"

Homebrew

  • brew tap vrrashkov/tokenparser
  • brew install tokenparser
  • 配置 yaml 配置文件
  • design_token_parser--generate--config"design_tokens_config.yaml"

配置

输入/输出路径,用于加载和生成
global:
  # Figma source paths
  # These are the pure files from Figma, they can contain aliases
  # For example if we have aliases we will need the actual value and not the alias
  # Separating different files is necessary in case there are duplicate trees but different values/aliases
  # So if we have button-md and button-big with the same trees but different values with aliases that need to be accesed from core.json
  # this should be the setup
  # Look at the figma/variables and figma/generated_styles for better understanding how it works
  figma_source_paths:
    - combine:
        file_name: "button-lg"
        files:
          - "assets/figma/variables/button-lg.json"
          - "assets/figma/variables/core-.json"
    - combine:
        file_name: "button-md"
        files:
          - "assets/figma/variables/button-md.json"
          - "assets/figma/variables/core-.json"
    - combine:
        file_name: "button-sm"
        files:
          - "assets/figma/variables/button-sm.json"
          - "assets/figma/variables/core-.json"
    - combine:
        file_name: "color-light"
        files:
          - "assets/figma/variables/color-accent-primary.json"
          - "assets/figma/variables/color-accent-secondary.json"
          - "assets/figma/variables/color-status-success.json"
          - "assets/figma/variables/color-status-danger.json"
          - "assets/figma/variables/color-light.json"
          - "assets/figma/variables/palette-.json"
    - combine:
        file_name: "color-dark"
        files:
          - "assets/figma/variables/color-accent-primary.json"
          - "assets/figma/variables/color-accent-secondary.json"
          - "assets/figma/variables/color-status-success.json"
          - "assets/figma/variables/color-status-danger.json"
          - "assets/figma/variables/color-dark.json"
          - "assets/figma/variables/palette-.json"
  # file_name: If set this will be the name of the merged file
  # if not, than the first file name will be used
  figma_output_paths:
    - combine:
        file_name: "button-lg"
        merge: ["button-lg"]
        files:
          - path: "assets/figma/variables/button-lg.json"
    - combine:
        file_name: "button-md"
        merge: ["button-md"]
        files:
          - path: "assets/figma/variables/button-md.json"
    - combine:
        file_name: "button-sm"
        merge: ["button-sm"]
        files:
          - path: "assets/figma/variables/button-sm.json"
    - combine:
        file_name: "color-light"
        merge: ["color-light"]
        files:
          - path: "assets/figma/variables/color-accent-primary.json"
            # if mode is set this will wrap the whole json object with a parent of the mode's value
            # this helps if you have similar trees in multiple files
            mode: "primary"
          - path: "assets/figma/variables/color-accent-secondary.json"
            mode: "secondary"
          - path: "assets/figma/variables/color-status-success.json"
            mode: "success"
          - path: "assets/figma/variables/color-status-danger.json"
            mode: "danger"
          - path: "assets/figma/variables/color-light.json"
    - combine:
        file_name: "color-dark"
        merge: ["color-dark"]
        files:
          - path: "assets/figma/variables/color-accent-primary.json"
            mode: "primary"
          - path: "assets/figma/variables/color-accent-secondary.json"
            mode: "secondary"
          - path: "assets/figma/variables/color-status-success.json"
            mode: "success"
          - path: "assets/figma/variables/color-status-danger.json"
            mode: "danger"
          - path: "assets/figma/variables/color-dark.json"
  #Output path
  style_output_path: "assets/generated_styles/"
模板配置
templates:
  - settings_general:
      generate_file_path: "generated_templates"
      file_name:
        format: "DS{{style}}"
        extension: "swift"
        #case: "kebab"
    settings_custom:
      # For header and footer {{style}} is a secial variable that can be used
      header:
        - "import SwiftUI"
        - "public class DSCore{{style}} {"
      footer:
        - "}"
      template_type:
        # For themes
        - type: color
          value: "public static let {{variable_name | camel}} = {{value | color: 'Color(red: rgb_r_v1, green: rgb_g_v1, blue: rgb_b_v1, opacity: rgb_a_v1)'}}  {{description | optional: '// desc = %value'}}"
        # For Core
        - type: string
          value: "public static let {{variable_name | camel}} = {{value}}  {{description | optional: '// desc = %value'}}"
        - type: float
          value: "public static let {{variable_name | camel}} = CGFloat({{value | as_text_or_number}})  {{description | optional: '// desc = %value'}}"
        - type: boolean
          value: "public static let {{variable_name | camel}} = {{value}}  {{description | optional: '// des        c = %value'}}"
        - type: composition
          value: "{% if verticalPadding != '' %} test1: {{verticalPadding | optional: 'vertical-padding-test-first: %value'}} {% endif %}"
        - type: composition
          value: "{% if verticalPadding != '' %} test2: {{verticalPadding | optional: 'vartical-padding-test-second: %value'}} {% endif %}"
        - type: boxShadow
          value:
            - "{{variable_name}} {{color-0 | color: 'hex'}} blur: {{blur-0}} x: {{x-0}}"
            - "{{variable_name}} {{color-0 | color: 'hex'}} {{color-1 | color: 'hex'}}  blur: {{blur-0}} x: {{x-0}} blur: {{blur-1}} x: {{x-1}}"

您可以使用每种类型多次以创建更清晰的方式创建您的值。有许多 过滤器 可以帮助您创建所需的模板(请查看以下内容)。此外,因为这个工具使用 Liquid,您可以在模板中使用每个过滤器/标签/块。如上所示,代码中有 if 语句检查变量是否存在,如果存在,则显示某些内容。

可以使用“可选”过滤器添加可选值。有时,使用“可选”过滤器而不是if语句更容易,只显示存在的值。

有效的JSON

两种类型的json都有效,因为存在正斜杠,所以可以有无限嵌套或根本不嵌套。

{
  "size/XL": {
    "type": "float",
    "value": "56",
    "description": ""
  },
  "text/fr": {
    "type": "string",
    "value": "Some Text",
    "description": ""
  },
  "color/bg": {
    "type": "color",
    "value": "#000000",
    "description": ""
  }
}
{
  "size": {
    "XL": {
      "type": "float",
      "value": "56",
      "description": ""
    }
  },
  "text": {
    "fr": {
      "type": "string",
      "value": "Some Text",
      "description": ""
    }
  },
  "color": {
    "bg": {
      "type": "color",
      "value": "#000000",
      "description": ""
    }
  }
}
JSON值格式

您可以根据需要无限地嵌套树形结构,只要最终节点包含valuetype(description是可选的)

有效示例及其使用JSON -> 模板

   "XL": {
        "type": "float",
        "value": "56",
        "description": ""
    }
- type: float
  value: "public static let {{variable_name | camel}} = CGFloat({{value | as_text_or_number}})  {{description | optional: '// desc = %value'}}"
public static let xl = CGFloat(56)

"bold": {
   "type": "typography",
   "value": {
      "fontFamily": "Noir Pro",
      "fontSize": "16",
      "fontWeight": "Bold",
      "letterSpacing": "-0.41",
      "lineHeight": "23"
    }
}
- type: typography
  value: 'public static let {{variable_name | camel}} = TextStyle(name: "{{fontFamily}}", size: {{fontSize}}, weight: TextStyle.Weight({{fontWeight | as_text_or_number}}), lineHeight: {{lineHeight}})'
public static let bold = TextStyle(name: "Noir Pro", size: 16, weight: TextStyle.Weight("Bold"), lineHeight: 23)

    "tabBar": {
      "type": "boxShadow",
      "value": [
        {
          "blur": "20",
          "color": "rgba(0,0,0,0.1)",
          "spread": "0",
          "type": "dropShadow",
          "x": "0",
          "y": "0"
        },
        {
          "blur": "8",
          "color": "rgba(0,0,0,0.1)",
          "spread": "0",
          "type": "dropShadow",
          "x": "0",
          "y": "4"
        }
      ]
    }
- type: boxShadow
  value:
    - 'public static let {{variable_name | camel}} = Shadow(x: CGFloat({{x-0}}), y: CGFloat({{y-0}}), color: Color(hex: "{{color-0 | color: ''hex''}}"), radius: CGFloat({{blur-0}}))'
    - 'public static let {{variable_name | camel}} = [Shadow(x: CGFloat({{x-0}}), y: CGFloat({{y-0}}), color: Color(hex: "{{color-0 | color: ''hex''}}"), radius: CGFloat({{blur-0}})), Shadow(x: CGFloat({{x-1}}), y: CGFloat({{y-1}}), color: Color(hex: "{{color-1 | color: ''hex''}}"), radius: CGFloat({{blur-1}}))]'
public static let shadowTabBar = [Shadow(x: CGFloat(0), y: CGFloat(0), color: Color(hex: "#00000019"), radius: CGFloat(20)), Shadow(x: CGFloat(0), y: CGFloat(4), color: Color(hex: "#00000019"), radius: CGFloat(8))]

"dissolve": {
  "description": null,
  "type": "custom-transition",
  "value": {
    "duration": 0.45,
    "easingFunction": {
      "x1": 0.6968395709991455,
      "x2": 0.06683959811925888,
      "y1": 0.05232666060328483,
      "y2": 0.9323266744613647
    },
    "transitionType": "dissolve"
  }
}
# Object
# {{easingFunction | empty}} use empty filter to initialize variable if you need it in the scope of the template without printing it
- type: "custom-transition"
  value: "public static let {{variable_name | camel}} = CustomTransition(duration: {{duration}},
    {{easingFunction | empty}}
    x1: CGFloat({{easingFunction.x1}}),
    x2: CGFloat({{easingFunction.x2}}),
    y1: CGFloat({{easingFunction.y1}}),
    y2: CGFloat({{easingFunction.y2}}))
  "
public static let dissolve = CustomTransition(duration: 0.45,  x1: CGFloat(0.6968395709991455), x2: CGFloat(0.06683959811925888), y1: CGFloat(0.05232666060328483), y2: CGFloat(0.9323266744613647))

"gradient": {
  "single with multiple color stops": {
    "description": "Four color stops from yellow to red",
    "type": "custom-gradient",
    "value": {
      "gradientType": "radial",
      "rotation": 180,
      "stops": [
        {
          "color": "#ffb800ff",
          "position": 0
        },
        {
          "color": "#ff8a00ff",
          "position": 0.34
        },
        {
          "color": "#ff2e00ff",
          "position": 0.65
        },
        {
          "color": "#ff0000ff",
          "position": 1
        }
      ]
    }
  }
}
# Array
# {{stops | empty}} use empty filter to initialize variable if you need it in the scope of the template without printing it
- type: "custom-gradient"
  value: "public static let {{variable_name | camel}} = CustomGradient(gradientType: {{gradientType}}, rotation: {{rotation}},
  {{stops | empty}}

  color1: Style1(
    {% for stop in stops %}

    {{stop.color | color: 'Color(red: rgb_r_v1, green: rgb_g_v1, blue: rgb_b_v1, opacity: rgb_a_v1)'}}
    {% if forloop.last == false -%}, {% endif %}
    {% endfor -%}
  ),

  color2: Style2(
    {% assign colors = stops | map: 'color' %}
    {% for item in colors %}

    {{item | color: 'Color(red: rgb_r_v2, green: rgb_g_v2, blue: rgb_b_v2, opacity: rgb_a_v2)'}}
    {% if forloop.last == false -%}, {% endif %}
    {% endfor %}
  )
  "
public static let defaultGradientSingleWithMultipleColorStops = CustomGradient(gradientType: radial, rotation: 180,
color1: Style1(
Color(red: 1.000, green: 0.722, blue: 0.000, opacity: 1.000) ,
Color(red: 1.000, green: 0.541, blue: 0.000, opacity: 1.000) ,
Color(red: 1.000, green: 0.180, blue: 0.000, opacity: 1.000) ,
Color(red: 1.000, green: 0.000, blue: 0.000, opacity: 1.000)  ),
color2: Style2(
Color(red: 255, green: 184, blue: 0, opacity: 184) ,
Color(red: 255, green: 138, blue: 0, opacity: 138) ,
Color(red: 255, green: 46, blue: 0, opacity: 46) ,
Color(red: 255, green: 0, blue: 0, opacity: 0)   )
}

使用{{easingFunction | empty}}空过滤器来初始化变量而不显示它。这个过滤器是这个库独有的,对于很多由液体模板提供的块是必要的。

关键词

您可以使用以下方式使用关键词: {{variable_name | kebab }} 关键词的名称,旁边您可以添加一个过滤器,或多个过滤器,用|分隔,如下所示 {{variable_name | kebab | no_space }}

特殊过滤器
名称 额外选项/信息
初始化变量而不显示其值
无空格 如果值包含空格,则将其删除。例如,“Test No Space”将变为“TestNoSpace
as_text_or_number 如果值是文本,则将其添加双引号。
pascal,
kebab,
camel
不同的案例过滤器
颜色 rgb_r_v1rgb_g_v1rgb_b_v1rgb_a_v1
rgb_r_v2rgb_g_v2rgb_b_v2rgb_a_v2
hex

v1 - 从0到255的值
v2 - 从0到1的值

您可以从资源文件夹中获取完整的配置示例

数组在模板化时有不同的处理方式。例如,文件中的boxShadow类型是这样工作的。因为我们可以预期有未知长度的数组值,所以可以这样处理。

# All the color related values from above
# For every new line of the boxShadow value, a new index can be used. For example:
# On line 1 you have only values with index 0
# On line 2 you have values with index 0 and 1
# On line 3 you have values with index 0, 1 and 2 and etc..
# All possible variants should be made with a template
# If there is a missing one you will be notified with an error to add it
- type: boxShadow
  value:
    - "{{variable_name}} {{color-0 | color: 'hex'}} blur: {{blur-0}} x: {{x-0}}"
    - "{{variable_name}} {{color-0 | color: 'hex'}} {{color-1 | color: 'hex'}}  blur: {{blur-0}} x: {{x-0}} blur: {{blur-1}} x: {{x-1}}"
Liquid过滤器

请从这里检查液体模板的可用过滤器/块等。

更多

对于任何想法或问题,请随时提问/报告。

依赖关系

~13MB
~233K SLoC