Figma 플러그인 개발기 🛠️- Select Frame To WebP

sumi-0011·2025년 1월 1일
3
post-thumbnail

들어가며

안녕하세요! 회사에서 불편함을 느껴 이를 개선하기 위해 Figma 플러그인을 개발하게 되었어요.

평소 프론트엔드 개발을 하면서 이미지 성능 최적화를 위해 WebP 포맷을 주로 사용하는데요, 피그마에서 이미지를 WebP로 변환하는 과정이 번거로워 이를 해결하고자 플러그인을 만들어보기로 했어요. 기존에 비슷한 플러그인이 있었지만, 제가 필요로 하는 기능들이 부족해서 직접 만들어보기로 결정했죠.

이 글에서는 피그마 플러그인을 어떻게 개발했는지, 그 과정에서 마주친 문제들을 어떻게 해결했는지 공유하려고 해요.

개발이 완료된 플러그인은 Figma Community에서 직접 사용해보실 수 있어요. 😉

코드의 퀄리티와 플러그인의 완성도보다는 개발 과정 자체를 공유하는 것에 초점을 맞췄으니 참고해서 봐주세요.

플러그인을 만들게 된 배경 🤔

기존 워크플로우의 문제점

회사에서 주로 디자이너들이 Figma에 이미지를 올려주고, 프론트 개발을 하며 이미지를 WebP로 다운받아 사용하는데, 이 과정에서 불편함을 겪었어요. Figma는 PNG, JPG, SVG 등 다양한 포맷으로 export를 지원하지만, WebP는 지원하지 않았거든요. 때문에 아래와 같은 번거로운 과정을 거쳐야 했어요

  1. Figma에서 PNG로 export (보통 x2 scale로)
  2. Squoosh로 이동해서 PNG를 WebP로 변환
  3. 변환된 파일을 다시 프로젝트에 적용

이 과정에서 파일을 하나씩 변환하고 확인하는 작업이 꽤 번거로웠어요. 저는 주로 Squoosh를 사용했는데요. 이 프로그램은 변환된 이미지의 용량이 얼마나 줄어들었는지 바로 확인할 수 있어요. 그런데 한번에 한 이미지만 변환할 수 있다보니, 매번 이 과정을 반복하면서 작업 흐름이 자주 끊기곤 했어요.

기존 플러그인의 한계

물론 Figma에도 'Webp Exporter'라는 플러그인이 있어요. 하지만 기본적인 WebP 변환 기능만 제공할 뿐, 제가 필요로 하는 기능들이 부족했죠. 그래서 기존 기능들을 포함하면서도 추가적인 기능을 갖춘 새로운 플러그인을 만들어보기로 했어요.

플러그인 기능 정의 🎯

구현하고자 하는 피그마 플러그인의 주요 기능은 아래와 같이 두 가지로 정리할 수 있어요.

1. 피그마에서 선택한 Frame을 PNG 이미지로 가져오기

피그마에서 선택한 Frame을 UI에 보여주어 사용자가 자신이 선택한 Frame이 무엇이고 어떤 이미지를 변환하려고 하는지 인지시키고, WebP로 변환하기 위해 PNG 데이터로 가져와요.

2. WebP 변환 (이미지 변환, 최적화)

  • 사용자가 선택한 PNG 이미지를 WebP 형식으로 변환
  • 품질(quality)과 크기 배율(scale) 조절 기능 제공
  • 브라우저의 Canvas API를 활용한 이미지 변환 및 최적화

추가 기능 💡

앞으로 구현하고자 하는 추가 기능들이에요.

  • 다운 이미지 배율, 이름 설정
  • 원본 PNG -> 변환 WebP 간 용량 차이 시각화
  • 다운 이미지 포맷 다중 선택 (WebP, PNG)
  • 변환 이미지를 S3에 업로드

이번 글에서는 플러그인 개발 전체 구현보다는 피그마 플러그인 내에서 피그마와 React 코드 간의 통신, 그리고 전체적인 WebP 변환/다운 기능에 초점을 맞춰볼게요.

플러그인 구현하기 🔨

1. 개발 환경 설정

Figma 플러그인 개발은 기본적으로 HTML, CSS, JavaScript만을 사용하도록 되어있어요.
하지만 저는 React가 더 익숙해서 Figma에서 제공하는 webpack-react샘플 코드를 사용했어요

Figma 플러그인 개발 가이드 : Plugin Quickstart Guide

플러그인은 크게 두 부분으로 나뉘어요

ui.tsx - 플러그인의 UI를 담당

function App() {
  const inputRef = React.useRef<HTMLInputElement>(null);

  const onCreate = () => {
    const count = Number(inputRef.current?.value || 0);
    // parent.postMessage로 피그마와 통신
    parent.postMessage(
      { pluginMessage: { type: "create-rectangles", count } },
      "*"
    );
  };

  return (
    <main>
      <header>
        <h2>Rectangle Creator</h2>
      </header>
      ...

여기서 parent.postMessage는 플러그인 UI와 Figma 간의 통신을 담당해요. pluginMessage에 type과 필요한 데이터를 담아 보내면, Figma 쪽에서 이를 받아 처리하는 구조예요.

code.ts - Figma와의 실제 통신을 담당

figma.showUI(__html__, { themeColors: true, height: 300 });

figma.ui.onmessage = (msg) => {
  if (msg.type === "create-rectangles") {
    // 메시지 타입에 따른 처리
    ...
  }
};

figma.showUI로 UI를 띄우고, figma.ui.onmessage로 UI에서 보낸 메시지를 받아 처리해요. 이렇게 UI와 Figma가 서로 메시지를 주고받으며 동작하는 구조예요.

2. Frame 선택 기능 구현

첫 번째로 구현한 건 Figma에서 선택한 Frame을 가져오는 기능이에요.
사용자가 변환하고 싶은 Frame을 선택하면, 그 Frame의 정보와 이미지 데이터를 가져와야 하죠.

const sendPngData = async () => {
  const node = figma.currentPage.selection[0];
  if (node) {
    // exportAsync로 PNG 데이터 추출
    const bytes = await node.exportAsync({ format: 'PNG' });
    figma.ui.postMessage({
      type: 'init-png-data',
      bytes: bytes,      // PNG 바이너리 데이터
      frameName: node.name,  // 선택한 프레임 이름
    });
  }
};

// 선택이 변경될 때마다 데이터 전송
figma.on('selectionchange', sendPngData);

여기서 node.exportAsync()는 선택한 Frame을 PNG 형식의 바이너리 데이터로 변환해요. 이렇게 얻은 데이터는 Uint8Array 형태로, 이후 WebP 변환에 사용돼요.

특히 figma.on('selectionchange', sendPngData)를 통해 사용자가 다른 Frame을 선택할 때마다 자동으로 데이터를 갱신하도록 했어요. 이렇게 하면 사용자가 여러 Frame을 연속해서 변환할 때 더 편리하죠.

3. PNG 데이터를 화면에 보여주기

이제 Figma에서 선택한 Frame의 PNG 데이터를 받아서 플러그인 UI에 표시해줄 차례예요. UI 코드는 React로 작성했는데, 여기서 중요한 건 Figma와 UI 사이의 메시지 통신이에요. React 코드에서는 useEffect 훅을 사용해서 이 통신을 처리했어요.

useEffect(() => {
    // message handler
    const messageHandler = (event: MessageEvent) => {
      const msg = event.data.pluginMessage;
      if (msg.type === 'init-png-data') {
        setInitPngData(msg);
      }
    };
    
    window.addEventListener('message', messageHandler);
    return () => {
      window.removeEventListener('message', messageHandler);
    };
  }, []);

이 코드에서 messageHandler는 피그마로부터 받은 메시지를 처리해요. 특히 'init-png-data' 타입의 메시지를 받으면 PNG 데이터를 상태로 저장하고 화면에 보여주게 되죠.

PNG 데이터를 UI에서 받아 미리보기를 보여주는 코드를 작성하면 플러그인 UI가 아래와 같이 보여요.

이어서 이 PNG 데이터를 WebP로 변환하는 과정을 설명해드릴게요.

3. PNG를 WebP로 변환하기

이제 PNG 데이터를 WebP로 변환하는 핵심 기능을 구현해볼게요.
이미지 변환에는 브라우저의 Canvas API를 활용했는데요, 코드의 가독성과 재사용성을 높이기 위해 관련 로직을 유틸리티 파일로 분리했어요.

첫 번째로, PNG를 Canvas에 그리는 함수를 만들었어요.

const _drawPngToCanvas = async (pngBytes: Uint8Array, scale: number) => {
  // PNG 바이너리 데이터로 Blob 생성
  const blob = new Blob([pngBytes], { type: 'image/png' });
  const image = new Image();
  image.src = URL.createObjectURL(blob);

  // 이미지 로드 완료 대기
  await new Promise((resolve) => {
    image.onload = resolve;
  });

  // Canvas에 이미지 그리기
  const canvas = document.createElement('canvas');
  canvas.width = image.width * scale;   // 배율 적용
  canvas.height = image.height * scale;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);

  return canvas;
};

이 함수는 Figma에서 받은 PNG 바이너리 데이터를 Canvas에 그리는 역할을 해요. 특히 scale 파라미터를 통해 이미지 크기를 조절할 수 있어요. 예를 들어 scale이 2라면 원본 크기의 2배로 이미지가 그려지죠.

그 다음으로는 Canvas의 내용을 WebP로 변환하는 함수를 만들었어요.

export const exportWebP = async (
  pngBytes: Uint8Array,
  fileName: string,
  quality: number,
  scale: number
) => {
  const canvas = await _drawPngToCanvas(pngBytes, scale);

  // Canvas를 WebP로 변환
  return new Promise((resolve, reject) => {
    canvas.toBlob(
      (webpBlob) => {
        if (webpBlob) {
          resolve(webpBlob);
        } else {
          reject(new Error('WebP 변환 실패'));
        }
      },
      'image/webp',
      quality / 100  // 품질 설정 (0~1)
    );
  });
};

이 함수는 Canvas에 그려진 이미지를 WebP 형식으로 변환해요. quality 파라미터를 통해 변환된 이미지의 품질을 조절할 수 있는데, 0부터 100 사이의 값을 받아요. 높은 값일수록 품질은 좋아지지만 파일 크기도 커지겠죠.

이렇게 변환된 WebP 이미지는 Blob 형태로 반환되어 다음 단계인 다운로드 기능에서 사용할 수 있어요.

4. 파일 다운로드 구현

마지막으로 변환된 WebP 파일을 다운로드할 수 있는 기능을 만들어볼게요. 브라우저에서 파일을 다운로드 받을 수 있도록 유틸리티 함수를 하나 만들었어요.

export const downloadFile = (blob: Blob, fileName: string) => {
  return new Promise<void>((resolve) => {
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    link.click();

    // Blob URL 정리
    setTimeout(() => {
      URL.revokeObjectURL(url);
      resolve();
    }, 1000);
  });
};

이 함수에서는 브라우저의 URL.createObjectURL()을 사용해서 파일 다운로드를 구현했어요. 특히 메모리 누수를 방지하기 위해 다운로드가 완료된 후에는 URL.revokeObjectURL()로 생성한 URL을 정리하는 것도 잊지 않았죠.

마지막으로, 변환과 다운로드를 연결하는 부분도 구현했어요.

try {
  const webpBlob = await exportWebP(pngBytes, fileName, quality, scale);
  await downloadFile(webpBlob, fileName);
  // 다운로드 완료 알림
  parent.postMessage({ pluginMessage: { type: 'export-complete' } }, '*');
} catch (error) {
  console.error('내보내기 중 오류:', error);
}

변환과 다운로드가 성공적으로 완료되면 Figma에 메시지를 보내서 사용자에게 완료 알림을 보여주도록 했어요. 혹시 오류가 발생하면 콘솔에 로그를 남겨서 디버깅할 수 있도록 했고요.

마치며 🎉

이렇게 해서 Figma에서 WebP 변환 작업을 훨씬 수월하게 할 수 있게 되었어요. 최종적으로 완성된 플러그인은 아래와 같이 동작해요

  1. Figma에서 Frame을 선택하면 자동으로 미리보기가 표시돼요
  2. 품질과 크기를 조절할 수 있어요
  3. 파일 이름도 원하는 대로 설정할 수 있어요
  4. 변환 버튼 하나로 WebP 파일이 바로 다운로드돼요

개발하면서 특히 신경 썼던 부분들이 있는데요.

  • 실시간으로 Frame 선택을 감지해서 바로 미리보기를 보여주도록 한 것
  • 메모리 누수를 방지하기 위해 Blob URL을 적절히 정리한 것
  • 사용자에게 진행 상황과 완료를 알려주는 피드백 시스템

물론 아직 개선하고 싶은 부분들이 있어요.

  • 여러 개의 Frame을 한 번에 처리하는 배치 기능
  • S3에 직접 업로드하는 기능
  • 더 세밀한 이미지 최적화 옵션들
  • 변환 전/후 용량을 비교할 수 있는 기능

이런 기능들은 차근차근 추가해볼 계획이에요. 이번 개발을 통해 Figma Plugin API와 Canvas API에 대해 많이 배웠는데, 이런 경험들을 바탕으로 더 유용한 기능들을 만들어볼 수 있을 것 같아요.

앞으로도 플러그인을 개선하면서 겪은 이야기들을 계속 공유해볼게요. 궁금한 점이나 제안하고 싶은 기능이 있다면 언제든 피드백 주세요!

만들어진 플러그인 확인해 보러 가기
👉 Figma Community - Select Frame To WebP

profile
안녕하세요 😚

0개의 댓글