Search
⚙️

[Tech] "딸깍"이면 끝! 피그마에서 바로 리액트 컴포넌트로 변환하기

태그
Frontend
날짜
2025/02/24
작성자

소개

안녕하세요, 라포랩스 프론트엔드 챕터 리드 김동욱입니다
이번 아티클에서는 제가 직접 개발한 피그마 플러그인을 활용해 피그마에서 바로 리액트 컴포넌트로 변환하는 과정과 이를 통해 생산성을 크게 향상시킨 경험을 공유하고자 합니다.
기존에는 피그마 디자인을 기반으로 프론트엔드 엔지니어가 속성(색상, variant 등)을 한 땀 한 땀 수작업으로 입력해야 했습니다. 이 과정은 시간이 오래 걸리고, 반복적인 작업인 만큼 생산성이 크게 떨어졌습니다.
‘더 나은 방법이 없을까?’ 고민하던 중 피그마 플러그인의 가능성을 발견했고, 직접 개발하는 것이 최적의 방법이라고 판단했습니다. 왜 굳이 직접 구축하는 것이 더 나은 선택이었는지, 그 이유에 대해 자세히 풀어보려 합니다.

피그마 플러그인 구조

피그마에는 다양한 형태의 플러그인을 만들 수 있습니다. 오늘은 codegen-plugin을 중심으로 살펴보겠습니다.
figma.codegen.on(”generate”) 의 콜백은 유저가 피그마에서 디자인 요소(node)를 클릭할 때마다 실행됩니다. 이 함수가 반환하는 값은 피그마의 오른쪽 패널에 표시됩니다.
예를 들어, 아래 코드는 간단한 플러그인을 구현한 사례입니다:
figma.codegen.on("generate", ({ node }) => { return [ { title: "라포랩스 플러그인입니다", language: "PLAINTEXT", code: "hello rapportlabs" } ] })
JavaScript
복사
위 코드를 적용하면, 피그마에서 어떤 요소를 클릭하더라도 오른쪽 패널에 "hello rapportlabs"가 표시됩니다.

1. 간단한 컴포넌트 변환하기

이제 리액트 컴포넌트로 변환하는 과정을 살펴보겠습니다. 프론트엔드 엔지니어라면 가장 많이 다루는 컴포넌트 중 하나인 Button으로 시작합니다.
라포랩스 디자인 시스템에서 버튼은 다양한 variant를 가질수 있고, 디자이너들은 피그마의 variant 기능을 활용해 다양한 가지 수를 표현합니다.
여기서 컴포넌트의 이름을 react component 이름으로 매핑하고, variant 속성을 react prop으로 활용하면 좋겠다고 생각했습니다. (시중 플러그인 또는 Figma의 CodeConnect도 여기까지는 지원해 줍니다.)
아래는 간단하게 작성해본 슈도 코드입니다:
figma.codegen.on("generate", ({ node }) => { // 컴포넌트 이름과 variant/property 정보 추출 const componentName = node.name const variantProps = node.componentProperties || {} // variant 속성 (예: { variant: "primary", size: "medium" }) // props를 JSX 형식으로 변환 const propsString = Object.entries(variantProps) .map(([key, value]) => { // boolean 값은 속성만 표시 (예: disabled="true" -> disabled) if (value === 'true') return key if (value === 'false') return '' // false는 생략 return `${key}="${value}"` // 일반 값은 key="value" 형식 }) .filter(Boolean) // 빈 문자열 제거 .join(' ') // 최종 JSX 코드 생성 return [ { title: `${componentName} 컴포넌트`, language: 'TYPESCRIPT', code: `<${componentName} ${propsString}>${children}</${componentName}>` }, ] })
JavaScript
복사
Figma Button 컴포넌트가 React 컴포넌트로 변환이 되었습니다. 이제 컴포넌트를 복붙해서 쓰면 됩니다!
위 코드를 보면 프론트엔드 엔지니어의 편의성을 위해 추가된 코드들을 볼수 있습니다.
예를 들어:
// boolean 값은 속성만 표시 (예: disabled="true" -> disabled) if (value === 'true') return key if (value === 'false') return '' // false는 생략
JavaScript
복사
이 부분은 리액트에서 일반적인 JSX 작성 관습을 반영한 겁니다. disabled="true" 대신 disabled만 쓰고, false일 때는 속성을 아예 생략하면 프론트엔드 엔지니어가 코드를 읽고 사용할 때 더 직관적이고 편리하죠.
위엔 없지만, Button 컴포넌트라면 자동으로 onClick prop을 제공해주는 것이 더 편리할 것입니다. 실제 플러그인에서는 이러한 세부적인 조정을 추가하여 프론트엔드 팀의 생산성을 높입니다.
시중에 공개된 플러그인이나 Figma의 CodeConnect를 써도 이런 회사 고유의 맥락을 반영하기는 어렵습니다.
예를 들어, 라포랩스에서 사용하는 디자인 시스템의 네이밍 규칙이나 prop 처리 방식을 그대로 녹여내려면, 결국 직접 구축하는 게 더 나은 선택이 됩니다. 이렇게 회사별 요구사항과 팀의 워크플로우에 맞춘 커스터마이징이 가능하다는 점에서, 직접 플러그인을 만드는 게 더 큰 가치를 발휘한다고 판단했습니다.

2. children을 가지는 경우

더 나아가, 컴포넌트 조합까지 자동으로 변환하는 기능을 추가했습니다.
Figma에서는 Frame Node 타입을 제공하며, 이는 Auto Layout이 적용된 컨테이너 역할을 합니다. 라포랩스 디자인 시스템에서는 VStack(Vertical Stack)과 HStack(Horizontal Stack)이 이런 역할을 합니다.
그래서 Frame Node를 만나면 정렬 방향에 따라 VStack이나 HStack으로 변환하고, 그 안에 children을 넣는 방식을 구상했습니다.
하지만 기존처럼 바로 JSX로 변환하는 방식에는 한계가 있었습니다. 피그마 노드들의 중첩된 트리 구조를 표현하기 위해서는 더 체계적인 접근이 필요했고, 때문에 AST(추상 구문 트리) 같은 중간 표현을 도입했습니다.
이를 위해 ReactNode 타입을 정의했습니다.
export interface ReactNode { type: string props: Props children: ReactNode[] | string comment?: string } export interface Props { className?: string style?: Style // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any | undefined }
JavaScript
복사
이제 변환 과정을 두 단계로 나눴습니다:
1.
피그마 노드를 먼저 ReactNode라는 중간 표현으로 변환하고,
2.
변환된 ReactNode를 JSX로 변환하는 별도의 함수를 적용하는 거죠.
이렇게 하면 중첩된 구조를 다룰 때 훨씬 깔끔하고, 나중에 다른 포맷으로 확장하거나 디버깅할 때도 유리합니다.
아래는 예시 슈도 코드입니다:
figma.codegen.on("generate", ({ node }) => { const reactNode = figmaNodeToReactNode(node) const jsx = reactNodeToJSXString(reactNode) return [ { language: 'TYPESCRIPT', code: jsx, title: 'JSX / TSX' } ] }) const figmaNodeToReactNode = (figmaNode): ReactNod => { switch (figmaNode.type) { case 'COMPONENT': return componentNodeToReactNode(figmaNode) case 'FRAME': return frameNodeToReactNode(figmaNode) } } const frameNodeToReactNode = (frameNode: FrameNode | InstanceNode): ReactNode => { const { name, layoutMode } = frameNode const children = frameNode.children.filter((child) => child.visible).map((child) => figmaNodeToReactNode(child))) const stackType = layoutMode === 'VERTICAL' ? 'VStack' : 'HStack' const style = getStyle(frameNode) return { type: stackType, props: { style, }, children, } }
JavaScript
복사
이제 버튼 2개와, 이를 감싸는 컨테이너까지도 리액트 컴포넌트로 예쁘게 변환할 수 있습니다!

3. 다양한 노드 타입으로 확장하기

피그마에는 다양한 노드 타입이 있고 이에 맞는 각 타입에 맞게 변환 코드를 작성해 주어야 합니다.
TextNode, GroupNode, RectangleNode 는 빈번하게 사용되는 타입이니 변환 코드 작성을 추천합니다.
const figmaNodeToReactNode = (figmaNode): ReactNode => { switch (figmaNode.type) { case "COMPONENT": return componentNodeToReactNode(figmaNode); case "FRAME": return frameNodeToReactNode(figmaNode); case "TEXT": return { type: "Text", props: { style: { fontSize: figmaNode.fontSize, fontWeight: figmaNode.fontWeight, }, }, children: figmaNode.characters || "", }; case "RECTANGLE": return { type: "Box", props: { style: { width: figmaNode.width, height: figmaNode.height, backgroundColor: figmaNode.fills[0]?.color, }, }, children: "", }; case "GROUP": return { type: "div", props: {}, children: figmaNode.children .filter((child) => child.visible) .map((child) => figmaNodeToReactNode(child)), }; default: return { type: "div", props: {}, children: "" }; } };
JavaScript
복사

4. 배포 관련

Figma의 Org 또는 Enterprise 플랜을 사용 중이라면 private plugin 배포를 통해 플러그인을 사내에서 쉽게 공유할 수 있습니다. 배포가 완료되면, 팀 내 모든 구성원이 별도의 설치 과정 없이 바로 플러그인을 사용할 수 있어 편리하죠.
반면, Org나 Enterprise 플랜이 아닌 경우에는 조금 번거로울 수 있습니다. 플러그인 코드를 GitHub에 업로드한 뒤, 각자가 직접 리포지토리를 클론해서 사용하는 방식으로 진행해야 합니다.

5. 부수적으로 얻은것

코드젠 과정에서 피그마의 컴포넌트 이름과 리액트 컴포넌트 이름이 다른 경우 변환하는 번거로움이 있었습니다.
하지만 굳이 다르게 할 필요가 있을까 고민한 끝에, 두 이름을 통합하기로 했습니다. 이렇게 하면 디자이너와 프론트엔드 엔지니어 간의 커뮤니케이션 비용이 줄어들 뿐만 아니라 코드젠이 컴포넌트 이름을 그대로 반영하니 프론트엔드 엔지니어가 임의로 다른 이름을 지을 이유도 없어졌습니다.
특히 신규 입사한 엔지니어에게도 이점이 큽니다. 피그마와 코드에서 이름이 동일하니 컴포넌트를 찾는 데 드는 시간이 확 줄어들죠!
라포랩스에는 아래와 같은 디자이너, 프론트엔드 엔지니어 가이드라인이 있습니다.
1.
피그마 컴포넌트 이름을, 그대로 프론트엔드 리액트 컴포넌트의 이름 으로 씁니다.
2.
피그마 컴포넌트의 variant와 property 이름을, 그대로 프론트엔드 컴포넌트의 prop 이름 으로 씁니다.
3.
이에 따라 디자이너는 컴포넌트 이름을 PascalCase로, variant, property 이름은 camelCase로 작성해주셔야 합니다.
4.
프론트엔드 엔지니어는 더 적합한 이름이 있다고 판단되면, 디자이너에게 이름 변경을 제안하는것을 권장합니다.

6. 정리

이제 라포랩스의 엔지니어는 “딸깍” 한 번으로 리액트 컴포넌트들을 복붙해서 사용할 수 있습니다.
현재 꽤 복잡한 중첩된 컴포넌트 변환까지 지원하며, 앞으로는 페이지 단위까지 정확도 높은 복붙을 목표로 합니다.

지금 바로 합류하세요

라포랩스는 4050 세대를 위한 라이프 스타일 플랫폼을 만들며, 사용자 경험(UX)을 최우선 가치로 삼고 있습니다.
유저가 더 쉽고 편리하게 서비스를 이용할 수 있도록, 프론트엔드에서 풀어야 할 과제들이 많습니다. 그래서 우리는 반복적인 작업을 자동화하고, 생산성을 극대화하여 진정 중요한 문제를 해결합니다.
우리는 단순히 코드를 작성하는 개발자가 아니라, 사용자 경험을 혁신하는 데 몰입하고 싶은 분을 찾습니다. 4050 세대의 삶을 더 편리하게 바꾸는 여정에 함께하고 싶다면 지금 바로 지원하세요