😁

FigmaからReactコンポーネントを生成しよう(Scripter編)

2025/02/21に公開

notahotel.comドメイン下には、物件の販売、キャンペーン、採用サイトといった静的ページを公開しています。2024年には、月3から8ページ、海外サイトを公開した7月には60ページを超える規模のページを制作しました。
フルタイムで開発するのは、僕一人。アルバイト一名と、業務委託が期間により数名という規模で開発しました。デザイナーが作成したデザインデータをもとに、愚直にコードに落とすこともできるが、コンポーネント化、データモデリングをしても、大変な規模になってきています。

そこで、FigmaからReactのコンポーネントを生成する仕組みを検討することにしました。

Animaあたりが有名なプラグインだが、
https://www.animaapp.com/figma

  • divタグだらけである
  • cssの値を少し変更したい時にできない
  • デザインシステムを徹底的にやることで、変数からタグが求めれるはず
  • オーナー向けアプリや社内ツールで使用しているTheme UIやAnt Designに対応していない

といった点が気になり、いい機会なので、seyaさんのZenn記事を見て、自前の変換プログラムを育てることにしました。
https://zenn.dev/seya/articles/105ab3e2864178

コードを作ろう

デベロッパーツールで触ってみる

Figmaを開いた状態で、適当にフレームを選択し、デベロッパーツールを開いて、以下のコードを実行すると、

figma.currentPage.selection

選択したフレームの情報が取れます。parent, childrenで、親子関係。typeを見ると、TextNodeであればpタグ。fillsを見れば、imgタグ, videoタグと判断できそうですね。
https://www.figma.com/plugin-docs/api/nodes/
https://www.figma.com/plugin-docs/api/Paint/
CSSについては、getCssAsyncで取得することができます。
https://www.figma.com/plugin-docs/api/WashiTapeNode/#getcssasync
Reactコンポーネントを作れそうですね。

Scripterを使って、生成してみる

ここからは、Scripterというプラグインを使います
https://www.figma.com/community/plugin/757836922707087381/scripter

こんな感じで、コードサジェストが効くので、気軽に使えます

選択したフレームからの木構造を生成し・・・

const getTree = async (node) => {
  if (node.visible === false) {
    return
  }
  if (node.type === 'VECTOR') {
    return
  }
  const style = await node.getCSSAsync()
  let children: any[] = []
  if (node.children) {
    for (let child of node.children) {
      const res = await getTree(child)
      children.push(res)
    }
  }
  const sourceId = node.type === 'TEXT' ? node.id : node.name
 // NOTE: コード生成したものと判断しやすくするため、N始まりにしている
  let id = 'N' + sourceId.replaceAll(/[ :;/()]+/g, '')
  let tagName = node.type === 'TEXT' ? 'p' : 'div'
  if (
    node.fills &&
    node.fills.length > 0 &&
    node.fills.find((fill) => fill.type === 'IMAGE')
  ) {
    tagName = 'img'
  }

  return {
    type: node.type,
    id: id,
    name: node.characters ?? node.name,
    style: convertStyle(node, style),
    tagName: tagName,
    children: children.filter((node) => node),
  }
}

let children: any[] = []
for (let node of selected) {
  const res = await getTree(node)
  children.push(res)
}

convertStyle部分は、Figmaから取得したCSSを改造したいので、入れています。

const convertStyle = (node, style) => {
  const after = { ...style }
  // NOTE: font-styleは使わない
  if (style['font-style'] === 'normal') {
    delete after['font-style']
  }
  // NOTE: backgroundは、まだ変数に落としていない
  if ('background' in style) {
    after['background'] = style['background'].replace(
      /var\(.+, (#[a-fA-F0-9]+)\)/,
      '$1'
    )
  }
  // NOTE: line-heightは、px指定をしたくない
  if ('line-height' in style) {
    const percent = style['line-height'].match(/\d+\.{0,1}\d*%/)
    if (percent?.length > 0) {
      const num = parseFloat(percent[0].replace('%', '')) / 100
      after['line-height'] = num
    }
  }
 // NOTE: 段落の場合、日本語をちょうどよく改行して欲しい
  if (node.type === 'TEXT') {
    after['word-break'] = 'auto-phrase'
  }
  return after
}

LPでは、ReactとStyledComponentsを利用しているので、コードに変換します。

const STYLED_COMPONENTS_TEMPLATE = `
const %id% = styled.%tag%\`
%style%
\`
`
const generateStyledComponents = (node) => {
  let array: string[] = []
  for (let k in node.style) {
    array.push(`${k}: ${node.style[k]}`)
  }
  const code = STYLED_COMPONENTS_TEMPLATE.replaceAll('%id%', node.id)
    .replaceAll('%tag%', node.tagName)
    .replaceAll('%style%', [...array, ''].join(';\n'))
  codes.push(code)
  node.children.forEach((node) => {
    generateStyledComponents(codes, node)
  })
}
const REACT_TEMPLATE = `
'use client'

import styled from 'styled-components'

const Component = () => {
  return (
    <>
    %code%
    </>
  )
}

export default Component

%styledCode%
`
const codeText = REACT_TEMPLATE.replace('%code%', children.join('')).replace(
  '%styledCode%',
  codes.join('')
)

インデントが気になるが、そこはVSCodeなり各エディター側で保存時にコード整形を走らせればOKとしています。コード追加は手間がかかるので、コード削除だけで済むレベルにすると良いです。

コードは、こちら
https://gist.github.com/tai-fukaya/fa6949b48f0e7fbc5e751c78f18b4fb0

まとめ

こういったコード生成のプラグインは、大体React, Tailwind CSSなので、他のライブラリを使っている場合でも、カバーできるのが良いですね。また、Scripterでコード実行することは、デザインシステムを進めるための調査、AIに読み取らせるための構造化のチェックでも使えると思います。

まずはScripterからコードでいじってみることから(デベロッパーツールでfigmaと叩くところから)、効率化を図ってみては、どうでしょうか。

静的ページの開発において、Figmaでできてしまうところはがっつり依存して、Figmaではできないところにクリエイティビティを注ぎこみたいですね。

ブランドサイトを開発するデベロッパー絶賛募集中です(副業は受け付けておりません)。
https://herp.careers/v1/notahotelinc/1p-TJ_N3o3WD

NOT A HOTEL

Discussion