초간단 피그마(Figma) 플러그인 만들기

Baesh·2023년 4월 23일
2

Side Project

목록 보기
1/1
post-thumbnail

들어가며

안녕하세요!👋

이번에 주말 동안 간단한 Figma(이하 피그마) 플러그인을 개발하게 되었는데요.

이 글을 통해 직접 경험한 피그마 플러그인 제작기와 예제 코드를 공유하고자 합니다.
코드의 퀄리티와 플러그인의 완성도 보다는 개발 과정 자체를 공유하는 것에 집중한 글이니 참고해서 봐주세요:-)

플러그인을 만들기로 한 이유

피그마 플러그인 개발에 도전하게 된 계기는 우연히 보게 된 글 (감자탕 먹고 Vue.js로 Figma 번역 플러그인 만든 이야기) 때문입니다. 이 글에서는 저와 비슷한 고민을 피그마 플러그인 개발을 통해 해소하였습니다.

저 또한 다국어를 지원하는 서비스를 개발하고 있었고 개발 과정에서 피그마 활용에 대한 고민이 있었습니다.

화면마다 별도의 언어를 작업하는 것은 피로도 있는 작업이며 디자이너, 개발자, 기획자별로 이를 관리하고 적용하는 경험은 늘 매끄럽지 않았던 것 같습니다. 따라서 비슷한 문제를 플러그인 개발로 해소한 위의 사례에 공감하게 되었습니다.

물론 직접 개발한 플러그인이 실제로 실무에서 효용이 있을지는 알 수 없고, 비슷하거나 더 나은 플러그인이 이미 존재할 수도 있습니다. 그러나 결심한 이상 일단은 만들어 보기로 합니다. 엉성한 바퀴를 다시 만들지라도 바퀴를 만들어 보는 경험을 가지는 것에 의미를 두고 플러그인 개발에 착수했습니다.

Deepl 번역기

플러그인 개발의 주요 목적은 화면 번역의 부담을 줄이는 것입니다. 따라서 성능 좋은 번역 API 선택은 필수적입니다.

다양한 번역 API가 존재하지만, 저는 그중 DeepL을 선택했습니다. Deepl'세계에서 가장 정확한 번역기' 라는 캐치프라이즈를 가지고 있는 인공 신경만 기반의 매우 정확한 번역기로 알려져 있습니다. 아직 사용해 보지 못했던 번역 API였기에 이번 기회를 통해 직접 사용해 보기로 하였습니다.

Rapid API 사용으로 결제 이슈 해결

다만 국내에서 DeepL API를 사용하기 위해서는 약간의 문제를 해결해야 했습니다. API 사용을 위해서는 신용카드 등록이 필수적이지만 현재 DeepL에서는 국내 신용카드 결제를 지원하지 않습니다.

결제 이슈로 인해 DeepL 사용을 포기하려던 찰나 Rapid API에서 제공하는 DeepL API를 찾을 수 있습니다. DeepL에서 공식적으로 등록한 것인지는 알 수 없지만 동일한 API를 제공하는 것으로 확인됩니다.

Rapid에서 국제결제가 가능한 국내 신용카드를 등록하고 무료 버전으로 구독하여 API KEY를 발급받을 수 있었습니다. 개발하는 피그마 플러그인은 해당 API KEY를 등록하여 DeepL번역 기능을 사용할 수 있도록 합니다.

피그마 플러그인 만들기

이제 서론을 끝마치고 본격적으로 피그마 플러그인을 만들어 보겠습니다.
피그마 플러그인은 피그마에 제공하는 공식 가이드(Plugin Quickstart Guide)를 따라한다면 어렵지 않게 개발할 수 있습니다.

공식가이드에서 제공하는 플러그인 제작 과정은 아래처럼 3단계로 요약할 수 있습니다.

플러그인 제작 과정
1. 프로젝트 생성
2. 코드 작성 (개발, 기능구현)
3. 플러그인 배포

이어서 해당 과정을 하나씩 살펴 보도록 하겠습니다.

프로젝트 생성

  1. 먼저 피그마 데스크톱 앱에 로그인하여 디자인 파일을 열어주세요.
  2. 상단 메뉴의 Plugins > Development로 이동한 다음 New Plugin...을 선택해 주세요.
  3. 피그마 디자인(Figma design)을 선택하고 플러그인 이름을 지정합니다.
  4. Custom UI를 선택하고 경로를 지정하여 저장합니다.
  5. 지정된 경로에 프로젝트 디렉토리가 생성되었다면 준비는 끝났습니다.

구현 과정

실제로 코드 작성이 요구되는 개발 과정 또한 복잡하지 않으며, 아래와 같이 3단계로 요약할 수 있습니다.

구현 과정
1. 화면구현
2. 기능구현
3. 컴파일

이어지는 글에서는 설명의 편의를 위해 3단계를 6단계로 세분화하고, 간단한 예제코드와 함께 구현과정을 살펴보도록 하겠습니다.

프로젝트 구조

먼저 VsCode로 생성된 피그마 프로젝트를 열어주세요.

프로젝트의 구성은 매우 심플해 보입니다. 프로젝트 구조는 크게 ui를 담당하는 ui.html과 기능을 담당하는 code.ts파일을 확인할 수 있습니다.

figma-plugin-project
├── README.md 
├── code.js // code.ts 컴파일된 결과
├── code.ts // 플러그인 코드 로직
├── manifest.json // 플러그인 관련 정보
├── package.json // 패키지 정보
├── tsconfig.json // 타입 스크립트 관련 설정 정보
└── ui.html // 플러그인 UI

1. 플러그인 화면 구현하기

플러그인 화면은 간단한 웹 개발 지식만 있다면 어렵지 않게 만들어 볼 수 있을것입니다. 피그마는 웹 기술(Figma의 기술 스택)을 통해 개발되었기 때문에, 피그마에서 동작하는 플러그인 또한 웹 기술로 구현할 수 있습니다.

이 예제는 화면구성과 기능이 간단하기 때문에 별도의 UI개발 프레임워크(라이브러리)는 사용하지 않습니다. 따라서 HTML, CSS, JavaScript만 을 사용하여 화면을 구현해 보도록 하겠습니다. 추가로 필요한 라이브러리는 CDN을 html 헤더에서 추가하여 사용합니다.

필요하다면 리액트와 같은 라이브러리를 사용하여 더 복잡한 화면을 개발할 수 있습니다. 피그마에서는 리액트로 구현한 플러그인 샘플 코드(React Plugin Sample)를 제공하고 있으니, 참고해서 필요한 기술을 선택해 주세요.

플러그인 화면 코드

ui.html

<body>
    <div class="info-container">
        <h1>Translation Plugin</h1>
        <div class="info-input-wrapper">
            <!-- API KEY 입력 인풋 -->
            <input
                id="apiKeyInput"
                type="text"
                class="text-input"
                placeholder="Rapid Api Key"
            />
            <!-- API KEY 저장 버튼 -->
            <button id="apiKeySaveButton" class="save-button">저장</button>
        </div>
    </div>
    <div class="content-container">
        <div class="select-box-wrapper">
            <div class="select-menu-wrapper">
                <!-- 대상언어 셀렉트 박스 -->
                <select id="uniqueId2" class="select-menu" disabled>
                    <option value="auto">언어 감지</option>
                </select>
            </div>

            <div>
                <span style="color: #0000001a"></span>
                <span style="color: rgb(51, 61, 75)"></span>
            </div>
            <div class="select-menu-wrapper">
                <!-- 변경언어 셀렉트 박스 -->
                <select id="targetLanguage" class="select-menu">
                    <option value="en">영어</option>
                    <option value="jp">일본어</option>
                </select>
            </div>
        </div>
        <div class="button-wrapper">
            <!-- 벅역 & 텍스트 교체 버튼 -->
            <button id="translateButton" class="button button--primary">
                텍스트 교체
            </button>
            <!-- 벅역 & 텍스트 추가 버튼 -->
            <button
                id="translateButtonAddSuggestion"
                class="button button--primary"
            >
                텍스트 추가
            </button>
        </div>
    </div>
    <hr />
    <!-- 개발자 정보 섹션 -->
    <div class="author-wrapper">
        <span class="auhtor-info-wrapper">
            <span class="author">baesh(sh.bae)</span>
            <span class="vertical-divider"> </span>
            <a
                class="author"
                href="https://rapidapi.com/splintPRO/api/deepl-translator"
                target="_blank"
                >deepL(Rapid API)</a
            >
        </span>

        <span class="icon-wrapper">
            <a href="https://github.com/baeseonghyeon" target="_blank">
                <i class="fa-brands fa-github"></i>
            </a>
            <a href="mailto: baesh.dev@gmail.com" target="_blank">
                <i class="fa-regular fa-envelope"></i>
            </a>
        </span>
    </div>
</body>     

2. 플러그인 기능 구현하기

구현하고자 하는 번역 플러그인에 필요한 주요 기능은 아래와 같이 두 가지로 정리할 수 있습니다.

1. API KEY 관리 (저장, 출력)

  1. 화면에서 API KEY를 입력하고 플러그인(코드)에 이를 저장한다.
  2. 플러그인(코드)에 저장된 API KEY를 화면에 표시한다.

화면에서 API KEY를 입력하고 이를 코드 단에 전달할 수 있어야, 사용자별로 다른 API KEY를 사용해 번역 API를 사용할 수 있을 것입니다. 더불어 한 번 전달받은 KEY는 휘발되지 않아야 합니다.

2. 번역 (번역, 결과물 출력)

  1. 화면에서 피그마 요소(text)를 선택하고 플러그인(코드)은 이를 번역한다.
  2. 플러그인(코드)에서 번역된 결과(text)를 피그마에 전달한다.

번역과 관련된 기능은 번역 API에서 제공하기 때문에, 번역에 대한 고민보다는 번역할 대상을 선택하고 번역된 결과물을 대상 요소에 적용하는 것이 전부인 것 같습니다.

이 글에서는 피그마 요소의 조작보다는, 플러그인 내부에서 데이터를 주고받는 과정에 초점을 맞추고자 합니다.
즉 UI에서 번역 API를 동작하기 위해 데이터를 전달하고 전달받는 방법플러그인에 데이터를 저장하는 방법을 중점적으로 살펴보겠습니다.

3. Message Event를 통한 데이터 통신

데이터 통신 관점에서 기능을 요약하면 다음과 같이 정리할 수 있습니다.

번역 플러그인 데이터 통신 플로우
1. 화면에서 코드로 데이터 전달하기
2. 화면에서 전달한 데이터 받기
3. 코드에서 화면으로 데이터 전달하기
4. 코드에서 전달한 데이터 받기

기본적으로 화면(ui.html)과 코드(code.ts)간의 함수 및 데이터 통신은 Message Event(MDM Web APIs - MessageEvent)를 통해 이루어집니다. Message Event는 서로 다른 html 간의 상호 데이터 처리를 위해 사용됩니다.

이를 사용하면 플러그인 UI(ui.html)와 Code(code.ts) 간의 데이터 통신 뿐만 아니라, 피그마 페이지(Figma.html)와 해당 페이지의 내부에서 동작하는 플러그인 페이지(PlugIn.html)간의 데이터 통신이 가능합니다.

화면에서 코드로 데이터 전달하기

아래는 플러그인 화면(ui.html)에서 번역 로직(code.ts)으로 데이터를 전달하는 간단한 예제입니다. 메시지 이벤트중 postMessage 를 사용하여 구현되었습니다.

화면(ui.html) → 로직(code.ts) 메시지 이벤트 스크립트

ui.html

<!-- POST MESSAGE -->
<script>
    // save 버튼을 클릭 했을때 인풋에 입력된 API KEY를 메시지로 전달하여 SET API KEY 로직을 수행합니다.
    document.getElementById("apiKeySaveButton").onclick = () => {
        const apiKeyValue = document.getElementById("apiKeyInput").value;

        if (apiKeyValue.length === 0) {
            showErrorMessage("Api Key를 입력해주세요.");
            return;
        }

        parent.postMessage(
            {
                pluginMessage: {
                    type: "setApiKey",
                    apiKey: apiKeyValue,
                },
            },
            "*"
        );
    };

    // 번역 버튼을 클릭했을때, translate 메시지를 요청하여 번역 로직을 수행합니다.
    document.getElementById("translateButton").onclick = () => {
        parent.postMessage(
            {
                pluginMessage: {
                    type: "translate",
                    target: targetLanguage,
                    isReplace: true,
                },
            },
            "*"
        );
    };
</script>

화면에서 전달한 데이터 받기

화면(ui.html)에서 전달한 값(data, 함수 호출)을 받는 코드(code.ts)

code.ts

figma.ui.onmessage = async (msg) => {
    if (msg.type === messageType.translate) {
        translateHandler(msg);
    }

    if (msg.type === messageType.setApiKey) {
        setApiKey(msg.apiKey);
    }

    if (msg.type === messageType.showFigmaErrorNotify) {
        showErrorMessage(msg.message);
    }
};

코드에서 화면으로 데이터 전달하기

반대로 번역 로직(code.ts)에서 화면(ui.html)에 처리된 결괏값과 같은 데이터를 전달해야 하는 경우 또한 필요합니다.
아래는 해당 케이스에 대한 예제 코드입니다.

로직(code.ts) → 화면(ui.html) 메시지 이벤트 스크립트

code.ts

// 피그마 스토리지에 값을 화면에 전달하는 init 함수
const init = async () => {
    try {
        const API_KEY = (await figma.clientStorage.getAsync("API_KEY")) ?? "";
        figma.ui.postMessage({ type: "send-apiKey", payload: API_KEY });
    } catch (err) {
        console.log(err);
    }
};

코드에서 전달한 데이터 받기

addEventListener를 사용하여 메시지 이벤트를 감지함으로써 로직(code.ts)에서 처리된 값을 전달받고, 화면 단에서 특정 함수를 실행할 수 있습니다.

로직(code.ts)에서 처리된 값(저장된 KEY, 번역 결과 등)을 받는 화면(ui.html) 스크립트

ui.html

<!-- GET MESSAGE -->
<script>
    window.addEventListener("message", (event) => {
        if (event.data.pluginMessage.type == "send-apiKey") {
            let apiKey = event.data.pluginMessage.payload;
            apiKeyInput.value = apiKey;
        }
    });
</script>

4. 값 저장 및 저장된 값 조회하기 (Client Storage)

한 번 입력해 둔 API KEY는 플러그인이 닫히더라도 사라지지 않고 저장되어야 합니다.
피그마 플러그인에서 제공하는 내장 메서드 figma.clientStorage를 사용하면, 데이터베이스 사용 없이 값을 메모리에 저장하여 사용할 수 있습니다. 이렇게 저장된 값은 플러그인이 닫혀도 유지됩니다.

데이터 저장 (Set)

code.ts

// API KEY 저장
const setApiKey = async (apiKey: string) => {
    try {
        await figma.clientStorage.setAsync("API_KEY", apiKey);
        figma.notify("API KEY가 저장되었습니다.");
    } catch (err) {
        console.log(err);
        showErrorMessage("API KEY 저장에 문제가 있습니다.");
    }
};

저장된 값 조회 (Get)

code.ts

// API KEY 로드 및 UI.html에 전달
const init = async () => {
    try {
        const API_KEY = (await figma.clientStorage.getAsync("API_KEY")) ?? "";
        figma.ui.postMessage({ type: "send-apiKey", payload: API_KEY });
    } catch (err) {
        console.log(err);
    }
};

5. typescript 컴파일하기

개발이 어느 정도 완료되었다면 피그마에서 플러그인을 실행해 보겠습니다.
피그마 플러그인은 기본적으로 브라우저에서 실행되고 브라우저는 JavaScript만 지원하기 때문에 매니페스트의 메인 필드는 TypeScript가 아닌 JavaScript 파일을 가리키게 됩니다.

즉 플러그인이 동작하기 위해서는 작성한 TypeScript가 코드가 JavaScript 컴파일되어야 합니다.

typescript를 컴파일하는 두 가지 방법

  1. VSCode에서 Hit Ctrl-Shift-B(Windows) or Command-Shift-B (Mac)을 통해 watch-tsconfig.json 를 선택하여 컴파일 합니다.

  2. 터미널에서 package.json에 선언된 스크립트를 순서대로 실행하여 코드를 컴파일 합니다.

    yarn build
    yarn watch

    package.json

    "build": "tsc -p tsconfig.json",
    "watch": "npm run build -- --watch"

컴파일링이 정상적으로 수행되었다면 code.js에서 컴파일링된 코드를 확인할 수 있습니다.

6. 플러그인 배포하기

이제 마무리 단계입니다.
완성된 플러그인은 피그마 커뮤니티에 배포(Publish)할 수 있습니다. 배포된 플러그인은 피그마 커뮤니티에 등록되며 프라이빗 플러그인을 제외한 모든 플러그인은 누구든지 사용할 수 있습니다.


플러그인 메뉴 중 배포할 플러그인의 우측 메뉴(···)를 누르고 Publish new version 을 클릭합니다.


배포를 위해서는 Publish 탭 Details의 필수 정보를 모두 채운 이후 Submit for review 버튼을 눌러 제출합니다.

플러그인이 커뮤니티에 등록되기 위해서는 피그마 팀의 검수(Review)와 승인 과정이 필요합니다. 피그마 팀에서는 요청된 플러그인이 커뮤니티에 등록되기 위한 기본 조건을 충족하는지 확인합니다.

검수는 작성된 정보부터 플러그인의 기능 동작과 코드 레벨까지 매우 꼼꼼하게 진행됩니다. 검수 결과에 따라 등록이 승인되거나, 등록이 반려될 수 있습니다. 반려된 경우에는 반려 사유와 피드백이 전달됩니다.

완성된 플러그인

완성된 번역 플러그인은 DeepL 번역기를 사용하여 선택한 텍스트 레이어의 언어를 대상 언어로 번역할 수 있습니다.

플러그인 기능

  • Rapid API KEY를 등록하여 DeepL번역기의 기능을 제공합니다.
  • 선택한 텍스트 레이어를 번역된 언어로 바꿀 수 있습니다.
  • 선택한 텍스트 레이어 근처에 번역 결과 주석을 추가할 수 있습니다.

플러그인 UI

번역 결과

간단한 기능의 플러그인인 만큼 그럭저럭 잘 동작하는 것 같습니다.
아래는 실제로 플러그인을 실행한 결과물입니다. 번역하고자 하는 레이어를 일괄 선택한 이후 플러그인의 텍스트 교체 혹은 텍스트 추가 버튼을 클릭하여 번역을 수행합니다.

  • 텍스트 추가 (좌)
    선택한 텍스트 레이어의 좌측에 번역된 텍스트 주석레이어를 추가합니다.
  • 텍스트 교체 (우)
    선택한 레이어가 원본 스타일과 동일하게 유지되며 변역결과로 교체 됩니다.

마치며

피그마 공식 문서를 따라 하며 간단하게 번역 플러그인을 만들어 볼 수 있었습니다. 이후에는 코드와 기능을 고도하거나 실제로 동료에게도 결과를 공유하고 피드백을 받을 일이 남았습니다.

처음으로, 또 빠르게 작업한 만큼 부족한 부분이 많습니다. 그러나 이번 경험을 기반으로 더 본격적이고 복잡한 플러그인 개발에 도전할 수 있을 것 같습니다. 또 다른 플러그인을 개발에 도전하게 된다면 다음에 다시 소개하도록 하겠습니다.

여러분들도 피그마 사용과 관련된 고민을 하고 계신다면 필요에 맞는 플러그인을 직접 개발하여 고민을 해소해 보는 것은 어떨까요?

긴 글 읽어주셔서 감사합니다!👏

profile
탐구하는 프론트엔드 개발자

2개의 댓글

comment-user-thumbnail
2023년 7월 14일

안녕하세요! TS2304에러가 계속 나는데 어떻게 해야할까요..?

1개의 답글