💙

作ろう!自分だけの Figma コード生成機✨

2021/04/22に公開

こんにちは。Figma プラグインを愛し、Figma プラグインに愛された男、そう、我こそは seya と申します。

先日 Figma から React のコードを生成するプラグインを公開しました。

https://www.figma.com/community/plugin/959795830541939498/Figma-to-React-Component

そして、これのソースコードをこちらのレポジトリにて公開しています。
面白いと思ったらぜひ Star お願いします!⭐️

https://github.com/kazuyaseki/figma-to-react

上記レポジトリは React を生成するためのものですが、React 以外にも色々な形で View を書いているところがあるでしょう。なのでこの記事では、上記レポジトリのコードを拡張・編集して「自分だけの Figma コード生成機」を作れるための解説を書きます。

ちなみにですが、 Fork して作っていただいたものでもご自由に Figma Community に publish などいただいて大丈夫です。何か文句を言うことは一切ありません。ただ「作ったよー」とご報告いただけると多分嬉しい気持ちにはなると思うのでご一報いただけますと幸いです!

はじめに

自分だけの Figma コード生成機を作りたいとなった時に、独自に作りたくなるのは大きく次の3パターンなのではないかなと思っています。

  1. React 以外の View の書き方で生成したい
  2. コンポーネントの記述を追加したい
  3. CSS の記述方法を増やしたい

これらを実装したいとなった時に、どこをいじればいいのかを解説するために次の順番で説明します。

  • Figma Plugin のアーキテクチャ
    • そもそも Figma Plugin というものがどういう環境で動いているのかを解説します
  • 中間表現の解説 - buildTagTree 関数
    • 本プラグインでは最終的にコードの文字列を組み立てる処理が書きやすくなるよう、中間表現として Figma のノードをツリー状に展開したものを生成しています。これがどんな型なのか、
  • コンポーネントごとに Tree を書き換え - modifyTreeForComponent
  • コード部分の文字列を生成する - buildCode
  • CSSの文字列を出力する - buildCssString

Figma Plugin のアーキテクチャ

Figma Plugin は実行環境として Figma の sandbox 環境とプラグインが走る環境の二つに分かれています。
前者は figma オブジェクトにアクセスすることができ、実際に Figma 上にあるオブジェクトの位置情報、スタイルの情報などを取得したり、新しいオブジェクトを作ったりすることができます。
後者はプラグイン自体の UI を作るもので普通の Web フロントエンド開発とほぼ同じ感覚で開発できます。外部にリクエストを飛ばしたりすることも可能です。

これら二つの環境は message 関数を通してデータのやり取りをしています。

例えば今回の Figma to React では次のような処理の流れになっています。

  • Sandbox 環境で Figma 上のオブジェクトを作ってコードの文字列を生成する
  • 作った文字列を figma.ui.postMessage 関数で UI スレッドの方に渡す
  • 受け取ったメッセージをプラグイン上で表示する

このように二つの環境を駆使して頑張って開発するのが Figma プラグイン開発です。どちらの環境も制約があるので少々面倒なこともありますが、基本的には制約はそんなに強くありません。

もっと詳しく知りたい方は下記ドキュメントをどうぞ。
https://www.figma.com/plugin-docs/how-plugins-run/

中間表現の解説 - buildTagTree 関数

type buildTagTree = (node: SceneNode): Tag | null

https://github.com/kazuyaseki/figma-to-react/blob/main/src/buildTagTree.ts

指定された Node を後々成形しやすいツリー上のオブジェクトに変換してる関数です。
今後他にもプロパティが増えたりするかもしれませんが、現状 Tag の型は次のようになっています。

type Property = {
  name: string
  value: string
  notStringValue?: boolean
}

export type Tag = {
  name: string
  node: SceneNode
  isImg: boolean
  isText: boolean
  textCharacters: string | null
  properties: Property[]
  css: CSSData

  children: Tag[]
}

名前(name)やテキストノードの場合はテキストノードの中身の文字(textCharacters)なんかを持っています。children には子ノードが Tag の型で入ってきており入れ子の構造になっています。

あと、元々の node も入れてあるので最悪なんでもできるようにはなっています。

コンポーネントごとに Tree を書き換え - modifyTreeForComponent

https://github.com/kazuyaseki/figma-to-react/blob/main/src/modifyTreeForComponent.ts

type modifyTreeForComponent = (tree: Tag, _figma: PluginAPI): Tag

この関数は再起的に Tag を見ていって、指定したコンポーネントの設定に合致する場合は Tag を書き換えています。

例えばコードベース内では Spacer というコンポーネントの設定を次のように記述しています。

{
    name: 'Spacer',
    matcher: (node: SceneNode) => {
      return node.name === 'Spacer' && (!('children' in node) || node.children.length === 0)
    },
    modifyFunc: (tag: Tag) => {
      if (tag.node.width > tag.node.height) {
        tag.properties.push({ name: 'height', value: tag.node.height.toString(), notStringValue: true })
      } else {
        tag.properties.push({ name: 'width', value: tag.node.width.toString(), notStringValue: true })
      }

      tag.isComponent = true
      return tag
    }
  }

matcher の部分にその Tag のノードがどういうプロパティを持っていたら Spacer コンポーネントと判定するのかの条件を記述します。

modifyFunc にはその Tag をどう書き換えるかを指定します。
例えば今回の場合、ノードの幅が高さより大きい場合に width プロパティを Props として描画できるように渡しています。

このように独自のコンポーネントの定義などを加えたい場合は、ここに追記していきます。

コード部分の文字列を生成する - buildCode

https://github.com/kazuyaseki/figma-to-react/blob/main/src/buildCode.ts

React のコードを生成する部分です。詳細は省いちゃってますが、結構泥臭く下記のように開始タグや子供のタグなどを描画するために一個一個関数を書いています。
他の View の書き方用に何かを作りたい時はこれと似たようなものを頑張って作ってください。

function buildJsxString(tag: Tag, cssStyle: CssStyle, level: number) {
  const spaceString = buildSpaces(4, level)
  const hasChildren = tag.children.length > 0

  const tagName = getTagName(tag, cssStyle)
  const className = getClassName(tag, cssStyle)
  const properties = tag.properties.map(buildPropertyString).join('')

  const openingTag = `${spaceString}<${tagName}${className}${properties}${hasChildren || tag.isText ? `` : ' /'}>`
  const childTags = buildChildTagsString(tag, cssStyle, level)
  const closingTag = hasChildren || tag.isText ? `${!tag.isText ? '\n' + spaceString : ''}</${tagName}>` : ''

  return openingTag + childTags + closingTag
}

export function buildCode(tag: Tag, css: CssStyle): string {
  return `const ${tag.name.replace(/\s/g, '')}: React.VFC = () => {
  return (
${buildJsxString(tag, css, 0)}
  )
}`
}

CSSの文字列を出力する - buildCssString

https://github.com/kazuyaseki/figma-to-react/blob/main/src/buildCssString.ts

最後に CSS 部分を生成するところです。
今回の例では Tag を再帰的に手繰りながら CSS の配列を作っています。
それから文字列を生成しています。
何らかの CSS 記法を対応したいかたはこの辺を頑張っていじってみてください。

Tag の中の CSS は buildTagTree のタイミングで Figma 上でのプロパティを CSS のに置き換える処理を行なっています。これらのプロパティ名 : 値 は基本的に Web ブラウザ上で動く CSS の値にしています。もし他のプラットフォームで作りたいという方がいらっしゃったらここを頑張っていじいじしてください。

export function buildCssString(tag: Tag, cssStyle: CssStyle): string {
  const cssArray = buildArray(tag, [])
  let codeStr = ''

  cssArray.forEach((cssData) => {
    const cssStr =
      cssStyle === 'styled-components'
        ? `const ${cssData.className.replace(/\s/g, '')} = styled.div\`
${cssData.properties.map((property) => `  ${property.name}: ${property.value};`).join('\n')}
\`\n`
        : `.${kebabize(cssData.className)} {
${cssData.properties.map((property) => `  ${property.name}: ${property.value};`).join('\n')}
}\n`

    codeStr += cssStr
  })

  return codeStr
}

ただ、この "配列にする" というやり方が適さないパターンもあると思っていて、例えば私は styled-components で作る時は一つ一つのタグを定義するというよりは次のように指定することが多いです。

const Hoge: React.VFC = () => {
  return (
    <Hoge>
      <div className="fuga">aaaaaa</div>
    </Hoge>
  )
}

const Hoge = styled.div`
  color: black;
  
  > .fuga {
    color: white;
  }
`

これ自動生成するの中々にロジックがハードになりそうだったので一旦諦めたんですが、将来的には配列に変換する以外の作り方も実装するかもしれないです。

また、そもそも入れられている CSS のプロパティが気に食わない、そもそもCSS以外のもの使いたいという場合はこちらのあたりのプロパティをゴニョゴニョいただけるといいと思います。

https://github.com/kazuyaseki/figma-to-react/blob/main/src/getCssDataForTag.ts

おわりに

以上、Figma コード生成機の内容を解説いたしました。
皆様の「俺だけの Figma コード生成機」作りをサポートできるだけの品質のコードになっていますと幸いです。

もし面白そうだな、と思ったらぜひぜひ作ってみてください。それではよき Figma ライフを!!

Discussion