[디자인 시스템] 10주 차 회고

GwangSoo·2024년 5월 31일
0

디자인 시스템

목록 보기
8/8
post-thumbnail

icon 자동화

이번주는 icon 자동화를 해보는 시간을 가졌다.

icona

블로그에서 소개한 icona를 이용하면 피그마에서 icon을 추출하여 깃허브에 있는 레포지토리로 가져올 수 있다.

사용 방법에 대해 간단히 알아보고자 한다.

1. icona 실행

우선 피그마 플러그인에서 icona를 실행시켜준다.

icona-1

위와 같이 icon을 추출할 깃허브 레포지토리 주소와 깃허브 토큰을 넣어주어야 한다.

  • 깃허브 토큰 생성 방법

    우선 프로필 → Settings에 들어간다.

    token-1

    왼쪽 메뉴 하단에 Developer Settings에 들어간다.

    token-2

    Personal access tokens에 있는 Tokens (classic)에 들어간다.

    token-3

    Generate new token (classic)을 이용하여 새로운 토큰을 생성한다.

    token-4

    이름을 설정해 주고 아래와 같이 체크를 해준다.

    token-5

    생성이 완료되면 토큰이 나오는데 토큰은 한 번만 나오니 꼭! 어딘가에 저장해 두어야한다.

    생성된 토큰을 위 icona에 넣어준다.

2. icona-frame 생성

피그마에서 frame을 생성(아무거나 해도 상관 없음!) 후 frame 이름을 icona-frame로 바꿔준다.

그런 다음 추출하고자 하는 icon들을 frame 안에 넣어준다.

icona-2

3. 레포지토리로 추출

플러그인에서 Deploy를 보면 아래 사진과 같이 나온다.

icona-3

Deploy를 누르게 되면 레포지토리에 자동으로 pull request가 생성된다.

icona-4

해당 pull request를 합치고 나면 아래와 같이 파일이 생성된다.

icona-5

이런식으로 피그마에서 svg를 추출할 수 있다.

component로 변환하기

해당 레포지토리는 터보레포를 이용한 모노레포로 구성되어 있다.

과정

// index.js
import fsPromise from "node:fs/promises";
import fs from "node:fs";
import path from "node:path";
import generateComponent from "./utils/generate-component.js";

async function main() {
  try {
    // 1️⃣ icons.json 파일 읽기
    const result = await fsPromise.readFile("../../.icona/icons.json", "utf8");
    // 2️⃣ 읽어들인 결과 JSON 형태로 변환
    const svgs = JSON.parse(result);

    // 3️⃣ 만들어진 icon 컴포넌트가 쓰여질 디렉토리 경로
    const icondirPath = path.resolve("./src/icons");

    // 디렉토리가 존재하지 않는다면 생성
    if (!fs.existsSync(icondirPath)) {
      await fsPromise.mkdir(icondirPath);
    }

    // 4️⃣ 모든 svg 파일을 컴포넌트로 생성
    await Promise.all(Object.entries(svgs).map(([name, value]) => generateComponent(name, value.svg)));
  } catch (err) {
    console.error(err);
  }
}

main();
// generate-component.js
import fs from "node:fs";
import prettier from "prettier";

/**
 * 컴포넌트를 생성하여 파일로 저장합니다.
 * @param {string} name - SVG 이름
 * @param {string} svg - SVG 내용
 */
const generateComponent = async (name, svg) => {
  const pascalCase = name[0].toUpperCase() + name.split("").splice(1).join("");

  // size와 fill 변수를 이용하기 위해
  // 고정되어있는 width, height, fill을 변수화 시키는 과정
  // 단, icons.json에 있는 svg의 크기가 24이고, 색상이 black이라는 가정하에 적용됨
  const replacedSvg = svg.replace(/"24"/g, "{size}").replace(/fill="black"/g, "fill={fill}");

  // 함수 내용
  const componentContent = `interface ${pascalCase}Props {
  size?: number;
  fill?: string;
}

function ${pascalCase}({ size = 24, fill = "black" }: ${pascalCase}Props): React.ReactElement {
  return (
  ${replacedSvg});
}

export default ${pascalCase};
`;

  // 함수 내용을 포멧팅하는 작업
  const formattedComponent = await prettier.format(componentContent, {
    parser: "typescript",
  });

  fs.writeFile(`./src/icons/${name}.tsx`, formattedComponent, "utf-8", (err) => {
    if (err) throw err;
  });
};

export default generateComponent;
  1. 루트에 있는 .icona 폴더에서 icons.json 파일을 읽는다.
  2. 읽어들인 값을 JSON 형태로 변환 해준다.
  3. icons 파일이 저장될 경로를 지정 해준다. 만약 경로에 디렉토리가 없다면 생성을 해준다.
  4. 해당 경로에 모든 icon에 대해 지정된 형식으로 파일을 작성 해준다.

결과물

// fire.tsx
interface FireProps {
  size?: number;
  fill?: string;
}

function Fire({ size = 24, fill = "black" }: FireProps): React.ReactElement {
  return (
    <svg
      width={size}
      height={size}
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <g id="fire">
        <path
          id="Vector"
          d="M17.66 11.2C17.43 10.9 17.15 10.64 16.89 10.38C16.22 9.78 15.46 9.35 14.82 8.72C13.33 7.26 13 4.85 13.95 3C13 3.23 12.17 3.75 11.46 4.32C8.87 6.4 7.85 10.07 9.07 13.22C9.11 13.32 9.15 13.42 9.15 13.55C9.15 13.77 9 13.97 8.8 14.05C8.57 14.15 8.33 14.09 8.14 13.93C8.08325 13.8825 8.03578 13.8248 8 13.76C6.87 12.33 6.69 10.28 7.45 8.64C5.78 10 4.87 12.3 5 14.47C5.06 14.97 5.12 15.47 5.29 15.97C5.43 16.57 5.7 17.17 6 17.7C7.08 19.43 8.95 20.67 10.96 20.92C13.1 21.19 15.39 20.8 17.03 19.32C18.86 17.66 19.5 15 18.56 12.72L18.43 12.46C18.22 12 17.66 11.2 17.66 11.2ZM14.5 17.5C14.22 17.74 13.76 18 13.4 18.1C12.28 18.5 11.16 17.94 10.5 17.28C11.69 17 12.4 16.12 12.61 15.23C12.78 14.43 12.46 13.77 12.33 13C12.21 12.26 12.23 11.63 12.5 10.94C12.69 11.32 12.89 11.7 13.13 12C13.9 13 15.11 13.44 15.37 14.8C15.41 14.94 15.43 15.08 15.43 15.23C15.46 16.05 15.1 16.95 14.5 17.5Z"
          fill={fill}
        />
      </g>
    </svg>
  );
}

export default Fire;

성공적으로 만들어진 것을 볼 수 있다.

추가로 생각해 봐야할 것

현재 storybook은 apps/docs에 위치해 있고, icons를 컴포넌트로 추출하여 담아두는 곳은 packages/ui에 있다.

  • 컴포넌트 생성 후 그에 맞는 스토리북 생성은 어떻게?
    hbs 파일을 이용하여 만들어야 하는데, 로직에 대해 고민을 더 해봐야 겠다.
  • package.json에 export하는 스크립트 추가하는 로직
    icons를 추출 후 apps/docs에 두게 된다면 간단하게 해결될 일이지만, story만 apps/docs에 있으면 하는 바람 때문에 고민이 많다... 그리고 만약 export를 하게 된다면 package.json에 컴포넌트를 명시해 주어야 하는데, 이 로직 또한 고민을 많이 해봐야 할 것 같다.

다음 주까지 할 일

  • preset, token, stylesheet 관련 자동화

0개의 댓글