はじめに
こんにちは。enechainで働いている takurinton です。
enechainではさまざまな開発者体験向上の取り組みが試行されていますが、今回は自分が主に見ているデザインシステムにフォーカスして記事を書こうと思います。
弊社のデザインシステムに関しては、 @Shunya078 の なぜ我々はデザインシステムを創るのか? を読んでいただくと背景がご理解いただけると思います。
今回書く開発者体験について
開発者体験の定義についてはさまざまな解釈があると思いますが、今回は以下の3つのトピックに絞って紹介します。
- eslint pluginによるコーディング規約の明文化
- Notionへのリソース集約
- デザイントークンと型定義
enechainのデザインシステムは有志で始められたプロジェクトであり、中心となる開発メンバーはいるものの、専任のコミッターはいません。
最近ではありがたいことに、プロダクトの開発メンバーから要望が出され、修正案をメンバー自身が実装して提案してくれることが増えてきました。コミットする人数が増えてきた今、開発者体験の向上は大切で、無視できない課題になっています。
具体的な試み
上記の3つのトピックに絞って、enechainのデザインシステムでの開発者体験向上の試みを紹介します。
eslint pluginによるコーディング規約の明文化
コードレベルで開発者間での迷いがないことは重要です。機械的に弾くことができる部分で迷いや議論が発生すると本来フォーカスするべき部分に集中するための時間や体力を奪われます。
しかし、コードを書いているときやコードレビューのときにそのような迷いが発生したことは誰しもあると思います。enechainのデザインシステムでは、そのような迷いが発生した場合、ADRに議論と意思決定の内容を記録し、実際にコードに落とし込むところまで行っています。
過去のシーンとして、enechainのデザインシステムでは変数の命名でのアンダースコア _
の使い方があやふやになっていたシーンがありました。
具体的には以下の2つが混合していた状態です。
- propsの名前がコンポーネントやhookの中で使っている名前と衝突する際の回避策としてのアンダースコア
- exportしない関数や変数のprefixとしてのアンダースコア
このようなコードが混在している状態では当然迷いが生まれます。実際に関数名の命名において迷いが生まれ、コードレビューを通して、以下のような会話をしました。
内容はコンポーネント内部でのみ使用される関数 handleCloseCalendar
の命名についてでした。この時は別途Slackで議論した後、ADRに決定した方針を記述し、この件はひと段落しました。
具体的な内容は以下のようになっています。ADRには背景とpros/cons、決定内容を記述します。
このような機械的に発見可能な内容はeslint pluginとして定義し、実際に導入することで人間が考えるリソースを極力減らしています。
実際に今回の議論の後に作成したルールは以下のリポジトリにあります。
https://github.com/takurinton/eslint-plugin-vars-name
初期実装は自分の手元で行いましたが、enechainでは全社で使用するeslint configやeslint pluginを集約しているリポジトリがあり、現在はそこに移植済みです。
Notionへのリソース集約
enechainのデザインシステムでは、notionに必要な情報が集約されています。
運用原則やデザイントークンについての内容をはじめ、ガイドラインやFigmaのリンク等もここに集約されており、ここを見れば大抵の開発や運用に必要な情報は集まっている状態になっています。
※ここに記載されている『株主総会』とは、デザインシステムの開発者や利用者を集めて成果やロードマップを発表する場の社内名称です。
例えば、enechainのデザインシステムではリリースにchangesetsを、付随するスタイルガイドのリリースにはArgoCDを使用しています。
changesetsとArgoCDのそれぞれのツールが初見の人でもわかるようにガイドを書くようにしています。
このように必要な情報を集約し周知することで、デザインシステムにコントリビュートしたい人や、デザインシステム自体について知りたい人が困らないようになっています。
デザイントークンと型定義
enechainのデザインシステムではデザイントークンの定義をFigmaで行なっています。作成したデザイントークンは Tokens Studio for Figma を使用してexportし、gitで変更を管理しています。
また、そのデザイントークンをTypeScriptのファイルに変換して、型をつけることで開発者体験を向上させています。
上記の2つの試みは一般的なプロダクト開発にも共通して必要なものですが、デザイントークンと型定義はデザインシステム特有の開発者体験向上の試みです。
実際にexportされたデザイントークンはJSONの形式になっており、以下のような形式をしています。
{ "global": { "spacing": { "0": { "value": "0", "type": "spacing" }, "0.5": { "value": "2", "type": "spacing" } // ...他のトークンが続く }, "PrimitiveColors": { "Gray": { "50": { "value": "#fafafa", "type": "color" } // ...他のトークンが続く } } // ...他のトークンが続く } }
JSONをそのままアプリケーションコードで扱うことは難しいため、このJSONからTypeScriptの型を生成するツールをvite pluginとして作成し、ビルド時にTypeScriptファイルを生成するようにしています。
例えば、spacingのtokenを吐き出すpluginは以下のようになっています。
// figma から吐き出したデザイントークン を import する import figmaTokens from "../../../src/theme/tokens.json"; type Tokens = typeof figmaTokens; const tokens: Tokens = figmaTokens; type SpacingValue = string; type Spacing = { [key: string]: SpacingValue; }; export const getSpacing = (): Spacing => { return Object.entries(tokens.global["spacing"]).reduce( (acc, [name, token]) => { if ("type" in token && token.type === "spacing") { acc[name] = `${token.value}px`; } return acc; }, {} ); };
この関数を使い、デザイントークンをTypeScriptファイルとして出力する関数は以下のようになっています。
ここでは、先ほどの関数を読み込み、その関数を実行し、その結果をTypeScriptファイルとして書き込むという処理を行なっています。
import * as prettier from "prettier"; import { getSpacing } from "./generator/spacing"; const TARGETS = [ { path: "src/theme/spacing.gen.ts", functions: [getSpacing], }, // ...その他のデザイントークンがここに続く ]; const generateThemePlugin = async (): Promise<null> => { const zx = await import("zx"); const options = (await prettier.resolveConfig("~/.prettierrc.js")) ?? {}; for (const target of TARGETS) { const types: string[] = []; types.push( "/*\n" + " NOTE: This file is generated by vite-plugin-gen-theme. DO NOT EDIT DIRECTLY.\n" + " To make changes, edit `src/theme/token.json` and run `pnpm build`.\n" + "*/" ); for (const fn of target.functions) { const name = objectName(fn.name); const type_ = JSON.stringify(fn(), null, 2); const text = `export const ${name} = ${type_} as const\n`; types.push(text); } // 書き込む zx.fs.writeFileSync( target.path, await prettier.format(types.join("\n"), options) ); } return null; };
生成されたTypeScriptファイルは以下のようになり、このobjectをchakra-uiの extendTheme に渡すことでデザイントークンに型をつけることができます。
/* NOTE: This file is generated by vite-plugin-gen-theme. DO NOT EDIT DIRECTLY. To make changes, edit `src/theme/token.json` and run `pnpm build`. */ export const Spacing = { "0": "0px", "1": "4px", "2": "8px", "3": "12px", "4": "16px", "5": "20px", "6": "24px", "7": "28px", "8": "32px", "9": "36px", "10": "40px", "12": "48px", "14": "56px", "16": "64px", "20": "80px", "24": "96px", "28": "112px", "32": "128px", "0.5": "2px", "-1": "-4px", "-2": "-8px", "-3": "-12px", "-4": "-16px", } as const;
また、ここまで行うと、デザイントークンを使用する際に補完が出るようになるので便利です。
おわりに
本記事ではenechainのデザインシステムでの開発者体験を向上させるための試みを紹介しました。
今回の試みによって、プロダクトのエンジニアがスムーズにコミットしてくれる機会が増え、さらに社内のデザインシステムが活性化しました。
開発者体験や仕組みの部分に関しては各社それぞれの取り組みが存在すると思いますが、この記事が誰かの参考になると嬉しいです。
enechainのデザインシステムはデザインシステムおよびUIライブラリとしてもまだまだ未熟な状態です。今後もプロダクトのためになるようなコードベースであり続けるために様々な開発者体験向上の方法を模索していきます。
enechainでは一緒に働く仲間を募集しています。