안녕하세요. 원스토어 iOS Platform팀 강석원입니다.
이번 포스팅에서는 Figma Variables을 추출하는 커스텀 플러그인 제작기에서 추출한 Variables 데이터를 Style Dictionary를 사용하여 코드로 변환하고, 이를 모든 플랫폼에서 활용할 수 있도록 적용해본 과정을 공유하려 합니다.
시작하기 전에...
Android, iOS, 그리고 Web은 각각 다른 플랫폼이기 때문에 사용하는 언어와 프레임워크가 모두 다릅니다. 따라서 같은 디자인 토큰을 사용하더라도, 코드 레벨에서 표현되는 산출물은 플랫폼마다 서로 다르게 나타납니다.
Color를 예로 들어 설명해보겠습니다.
- Android 개발자는 8자리 16진수로 색상을 정의하고, 이를
colors.xml
파일에 저장합니다. - iOS 개발자는 색상을 RGBA 형식으로 구성된
.json
파일에 정의합니다. - Web 개발자는 색상을 RGB 형식으로 작성하고, 이를
.css
파일에 정의합니다.
이처럼 같은 디자인 토큰이라도 플랫폼마다 필요한 요구사항과 규칙이 존재하기 때문에 서로 다릅니다. Product의 기본이 되는 디자인 토큰이 달라진다면 디자인 일관성을 헤칠 수 있기 때문에 반복적인 이런 작업을 자동화 해보려고 합니다.
Style Dictionary란
Style Dictionary는 Amazon의 Danny Banks가 만든 오픈소스입니다. Style Dictionary는 스타일을 한 번만 정의하면 단일 명령어로 어떤 플랫폼이나 언어에서도 요구사항에 맞는 파일로 생성해주는 도구 입니다.
즉 디자인 토큰을 한 곳에서 관리하고 다양한 플랫폼에 일관성 있게 내보낼 수 있는 도구입니다.
Build Process
프로세스를 하나씩 차근차근 이해해보겠습니다.
(1) Deep Merge
Style Dictionary는 모든 디자인 토큰 파일을 가져와 Deep Merge를 수행합니다. 이렇게 하면 토큰 파일이 여러개로 구성되어 있더라도 원하는 방식으로 구성하여 파일을 생성할 수 있습니다.
(2) Transform Tokens
이 단계에서는 Transform(변환) 과정을 통해 통합된 디자인 토큰 객체를 각 플랫폼에서 이해할 수 있는 형태로 변환합니다. 변환 과정은 Style Dictionary의 핵심이며, 크게 네 가지 주요 작업으로 나뉩니다.
a. Transforms
Transforms
는 디자인 토큰의 값을 각 플랫폼이 요구하는 형식으로 변환합니다. 예를 들어, 색상 값을 표현하기 위해 Android는 8자리의 16진수 값이 필요하고 iOS에서는 RGBA 값이 필요합니다. 이때 Transforms 단계에서는 이를 각 플랫폼 요구사항에 맞게 변형시킬 수 있습니다.
b. Replace References
Style Dictionary는 토큰 객체를 다시 한 번 순회하면서 참조(alias)값을 처리합니다.
참조값이란, {Color.White.W100}
와 같이 다른 토큰을 가리키는 값입니다. 이 단계에서는 해당 참조값을 찾아 실제 변환된 값으로 대체합니다.
{
"Sementic-Color": {
"Bg": {
"Default": {
"$type": "color",
"$value": "{Color.White.W100}",
"comment": "Default background color"
}
}
}
}
- 예:
{Color.White.W100}
→#FFFFFF
c. Format Tokens
Transforms과 Replace References 과정을 마친 후, 이제 디자인 토큰 객체를 플랫폼별로 지정된 파일 형식에 맞게 포맷팅합니다.
- 예를 들어, Web에서는 CSS 변수(
.css
), iOS에서는 Swift 파일(.swift
), Android에서는 XML 파일(.xml
) 형식으로 토큰 데이터를 작성합니다.
d. Actions
Actions는 파일이 생성된 후 실행되는 커스텀 작업입니다.
- 예: 생성된 파일을 특정 디렉토리로 복사하거나, 이미지 또는 아이콘 파일을 생성하는 작업.
Actions는 각 플랫폼의 빌드 프로세스에 맞게 추가 작업을 수행할 수 있도록 해줍니다.
Style Dictionary 적용해보기
이제 본격적으로 Style Dictionary를 적용해보겠습니다.
Style Dictionary 문서에는 쉽게 시작할 수 있도록 Basic-Example를 제공하고 있습니다.
.
├── README.md
├── config.json
├── tokens/
│ ├── color/
│ │ ├── base.json
│ │ └── font.json
│ └── size/
│ └── font.json
└── build/
├── android/
│ ├── font_dimens.xml
│ └── colors.xml
├── compose/
│ ├── StyleDictionaryColor.kt
│ └── StyleDictionarySize.kt
├── scss/
│ └── _variables.scss
├── ios/
│ ├── StyleDictionaryColor.h
│ ├── StyleDictionaryColor.m
│ ├── StyleDictionarySize.h
│ └── StyleDictionarySize.m
└── ios-swift/
├── StyleDictionary.swift
├── StyleDictionaryColor.swift
└── StyleDictionarySize.swift
Basic-Example의 디렉터리 구조는 이렇게 구성되어 있습니다:
- tokens/: 디자인 토큰 파일이 여기에 위치합니다. Variables Exporter 플러그인에서 추출한 디자인 토큰 파일을 여기에 위치시키면 됩니다.
- build/: Style Dictionary가 빌드하면 결과물이 저장되는 경로입니다. (저장 경로는
config.json
에서 수정할 수 있습니다.) - config.json: Style Dictionary는
config.json
에 따라서 빌드됩니다.
1. Config.json 변경
Style Dictionary를 빌드하기 위해서는 config.json
파일을 수정해야 합니다. Deep Merge된 디자인 토큰을 사용해 빌드하면 최종적으로 아래와 같은 결과물이 생성됩니다.
// Generated on 12/8/24
/*------------------------------------------------------------------------------
* PROJECT : ONE store Design System
* NAME : ODSColor+Extension.swift
* DESC : Do not edit directly, this file was auto-generated.
* Copyright 2024 OneStore All rights reserved
*------------------------------------------------------------------------------*/
import SwiftUI
public extension ODSColor {
private static let ODSstyleBundle: Bundle? = .init(for: ODSResourcesBundle.self)
/// Default background color
static let bgDefault = Color("bgDefault", bundle: ODSstyleBundle)
/// Brand background color
static let bgBrand = Color("bgBrand", bundle: ODSstyleBundle)
...
}
이 파일을 자동으로 생성하기 위해 config.json
파일은 아래와 같이 구성됩니다:
{
"source": ["tokens/**/*.json"],
"platforms": {
"ios": {
"buildPath": "build/ios/",
"files": [
{
"destination": "ODSColor+Extension.swift",
"format": "ios-color-addition",
"options": {
"fileHeader": "ios-customHeader"
}
}
]
}
}
}
이제 빌드 결과물로 생성된 ODSColor+Extension.swift
파일과 config.json
을 비교하며 주요 요소를 분석해보겠습니다.
(1) source
추출한 디자인 토큰 파일들의 경로를 지정합니다. 여기서는 tokens
하위에 있는 모든 .json
파일을 포함하도록 설정했습니다.
(2) platforms
platform
은 특정 플랫폼으로 출력하기 위해 디자인 토큰을 변환하고 포맷하는 방법을 Style Dictionary에 알려줍니다. 필요한 만큼의 플랫폼을 정의할 수 있으며, 이름도 자유롭게 지정 가능합니다.
(3) buildPath
빌드 결과물이 저장될 파일 경로를 지정합니다. 여기서 설정한 경로에 빌드된 결과물이 생성됩니다.
(4) destination
생성할 파일의 이름을 정의합니다. 여기서는 ODSColor+Extension.swift
를 생성하도록 설정했습니다.
(5) fileHeader
ODSColor+Extension.swift
파일의 상단에 추가되는 헤더를 정의합니다. Style Dictionary는 파일에 삽입될 헤더를 자유롭게 커스터마이징할 수 있습니다.
export const iosCustomHeader = {
name: "ios-customHeader",
fileHeader: (fileName) => {
const nowDate = getNowDateToString();
return `// Generated on ${nowDate}
/*------------------------------------------------------------------------------
* PROJECT : Wheel Design System
* NAME : ${fileName}
* DESC : Do not edit directly, this file was auto-generated.
* Copyright 2024 OneStore All rights reserved
*------------------------------------------------------------------------------*/`;
},
};
function getNowDateToString() {
const now = new Date();
const year = now.getFullYear().toString().slice(-2);
const month = (now.getMonth() + 1).toString();
const day = now.getDate().toString();
return `${month}/${day}/${year}`;
}
_StyleDictionary.registerFileHeader(iosCustomHeader);
_StyleDictionary.buildAllPlatforms();
ios-customHeader
는 파일 생성 날짜와 파일에 대한 설명 주석을 추가합니다. 이를 registerFileHeader()
함수로 등록하면 config.json
파일에서 해당 헤더를 사용할 수 있습니다.
(6) format
Style Dictionary 빌드 프로세스에서 언급한 Format을 사용자 정의할 수 있습니다. 생성될 ODSColor+Extension.swift
파일의 구조를 보면, 헤더 작성, SwiftUI
import, ODSColor
extension 생성, 그리고 디자인 토큰 변수를 포맷에 맞게 추가하는 작업이 필요합니다.
export const iosColorAdditionFormat = {
name: "ios-color-addition",
format: function ({ dictionary, platform, options, file }) {
const colors = dictionary.allTokens
.filter((token) => token.attributes.category === "Sementic-Color")
.map(formatSwiftColorToken)
.join("\n");
return `${options.fileHeader(file.destination)}
import SwiftUI
public extension ODSColor {
private static let ODSstyleBundle: Bundle? = .init(for: ODSResourcesBundle.self)
${colors}
}
`;
},
};
function formatSwiftColorToken(token) {
const name = convertPathToName(token.path);
let comment = "";
if (token.comment) {
const cleanComment = token.comment.replace(/\n/g, " ");
comment = ` /// ${cleanComment}\n`;
}
return `${comment} static let ${name} = Color("${name}", bundle: ODSResourcesBundle)`;
}
_StyleDictionary.registerFileHeader(iosCustomHeader);
_StyleDictionary.registerFormat(iosColorAdditionFormat);
_StyleDictionary.buildAllPlatforms();
registerFormat()
함수를 통해 등록하면 config.json
에서 자유롭게 사용할 수 있습니다.
이제 추출한 토큰을 기반으로 ODSColor+Extension.swift
파일을 자동으로 생성할 준비가 완료되었습니다.
$ style-dictionary build
이 명령어 하나로 모든 디자인 토큰 변경 사항에 대응하고, 일관된 UI를 제공할 수 있게 됩니다. 한 번만 디자인 토큰을 정의해두면 이후에는 style-dictionary build
명령어로 간편하게 업데이트를 반영할 수 있습니다.
마치며
Style Dictionary를 적용하는 과정에서 러닝 커브가 존재하기 때문에 초반에는 다소 번거롭다고 느껴질 수 있습니다. 하지만 디자인 시스템의 주요 목표 중 하나인 일관된 UI 제공을 위해, 이러한 초기 비용은 장기적으로 매우 의미 있는 작업이 될 것이라 생각합니다.
다음 포스팅에서는 지금까지의 과정을 기반으로 CI/CD를 적용해 자동화하는 방법을 소개하겠습니다. 감사합니다!