Slide 1

Slide 1 text

18営業日で350コンポーネント規 模のVueアプリにデザインシステ ムを導入する方法
 baseballyama


Slide 2

Slide 2 text

自己紹介 (baseballyama)
 ● 株式会社フライル
 ○ TypeScript / Vue / Svelte
 ○ Kotlin / Spring Boot
 ○ Python / Fast API / Open AI
 ○ AWS / Terraform
 ○ MySQL / Fargate / S3 / SQS / その他 
 
 ● Svelteコアチームメンバー
 ○ Svelte5に向けて活動中
 ○ 少しだけ Svelte用 ESLintプラグイン 
 
 ● その他
 ○ 最近はひたすらPythonを書いている 
 ■ この話は来月の JSConf JP で... 


Slide 3

Slide 3 text

2023/4/25 (水) 〜2023/5/25 (木) の18稼働日で
 デザインシステムを導入した奮闘記


Slide 4

Slide 4 text

本日みなさんにお伝えしたいこと
 ● Big Bang リファクタリングを実施した経験談
 ● Big Bang リファクタリングを支援する強力な武器の紹介
 ○ (あらゆるリファクタリングに適用できます!)


Slide 5

Slide 5 text

● 前提
 ○ プロジェクトの概要
 ○ Big Bang vs Incremental 
 ● プロジェクト管理面での工夫
 ○ Big Bang を選択した理由 
 ○ 我々が重要視したこと 
 ○ スイッチングコストを極限まで減らす 
 ○ 重要でないページの細かいデザイン調整は後回しにする 
 ○ 進捗は数値で見える化 
 ○ マニュアルテストを複数回実施 
 ○ デザイナーとのペアデザイン 
 ● 技術面での工夫
 ○ ESLintのカスタムルールでレビューはできる限り自動化 
 ○ 可能な限り自動マイグレーション (今回の目玉) 
 ○ デザインコンポーネントの破損はVRTで検査 
 ○ E2Eテストで重要・頻出導線の破損を防ぐ 
 アジェンダ


Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

今回の発表は、セクションの区切りごとに DALL·E 3 で描いた画像を挿入してみました。 但し、1枚だけ DALL·E 3 で描いていない画像が1枚あります。
 DALL·E 3 で描いていない画像はどれなのか予想しつつ、引き続きお楽しみくださいま せ。


Slide 8

Slide 8 text

前提


Slide 9

Slide 9 text

今回扱うプロジェクトの使用技術
 ● Vue3.3
 ● TypeScript 5.2
 ● Vite 4.5
 ● Storybook / Chromatic
 ● ESLint
 ● Stylelint


Slide 10

Slide 10 text

プロジェクトの概要
 ● プロジェクト規模 (2023/10/28 (土) 現在)
 
 
 
 ※ それ以外のファイル (例: Svelte) は省略
 ● プロジェクトのスコープ
 ○ UIコンポーネント (35個) は既に実装済み 
 ■ (UIコンポーネントの設計やデザイン原則などは今回の発表の対象外) 
 ○ UIコンポーネントをプロダクトに取り込む作業が今回の対象 
 
 ファイル種別
 ファイル数
 行数
 Vue
 432
 71,115
 TypeScript
 765
 9,519


Slide 11

Slide 11 text

プロジェクトの概要
 ● (デザイン原則を含む) デザインシステムを導入した理由
 ○ サービス全体で一貫したデザインにするため
 ■ 一貫したデザインはユーザーを迷わせません
 ○ UIの実装を迅速にするため
 ■ もう font-size を指定する必要はありません 😄
 ● なぜ今だったのか
 ○ いわゆる大企業と呼ばれる企業様への導入が進行中
 ■ ユーザー数が増える前に大幅なデザイン変更を完了させたかった
 ○ 開発者数の増加
 ■ 開発者が増えるほどデザインシステムによる効率化効果が増す


Slide 12

Slide 12 text

参考: 新旧デザイン
 旧デザイン
 新デザイン


Slide 13

Slide 13 text

Big Bang vs Incremental
 Big Bang (一度に大規模な変更を適用) 
 ● 計画とテストが非常に重要
 ● 一気に大量のコードを変更するのでバグの原因特定が困難 
 ● 機能開発やバグ修正と並走できない (コンフリクトする可能性が高いため) 
 
 Incremental (小さなステップに分割して変更を適用) 
 ● 計画の変更が容易
 ● テストを頻繁に実行するため、リスクを低減 (分散) できる 
 ● 機能開発やバグ修正と並走できる 
 
 多くの場合、Incrementalが安全で効率的です。ただし、状況によりBig Bangも選択肢となる場合があります。 


Slide 14

Slide 14 text

Big Bang を選択した理由
 ● アプリの一部が新デザイン、一部が旧デザインは明らかに奇妙だったから


Slide 15

Slide 15 text

参考: 旧デザイン


Slide 16

Slide 16 text

参考: 新デザイン


Slide 17

Slide 17 text

我々が重要視したこと
 
 ● 最短期間でリリースする
 ○ 特にクリティカルなバグが見つかった時に対応できない期間を最小化するため 
 最短期間でリリースするために...
 ● 1⃣ (プロ管) スイッチングコストを極限まで減らす 
 ● 2⃣ (プロ管) 重要でないページの細かいデザイン 調整は後回しに
 ○ マージン・パディングなど... 
 ● 3⃣ (プロ管) 進捗は数値で見える化 
 ● 6⃣ (技術) ESLintのカスタムルールでレビューは できる限り自動化
 ● 7⃣ (技術) 可能な限り自動マイグレーション (今 回の目玉)
 品質を維持するために...
 ● 4⃣ (プロ管) マニュアルテストを複数回実施 
 ● 5⃣ (プロ管) デザイナーとのペアデザイン 
 ● 8⃣ (技術) デザインコンポーネントの破損はVRT で検査
 ● 9⃣ (技術) E2Eテストで重要・頻出導線の破損を 防ぐ


Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

プロジェクト管理面での工夫


Slide 20

Slide 20 text

我々が重要視したこと
 
 ● 最短期間でリリースする
 ○ 既存機能にクリティカルなバグが見つかった時に対応できない期間を最小化するため 
 最短期間でリリースするために...
 ● 1⃣ (プロ管) スイッチングコストを極限まで減らす 
 ● 2⃣ (プロ管) 重要でないページの細かいデザイン 調整は後回しに
 ○ マージン・パディングなど... 
 ● 3⃣ (プロ管) 進捗は数値で見える化 
 ● 6⃣ (技術) ESLintのカスタムルールでレビューは できる限り自動化
 ● 7⃣ (技術) 可能な限り自動マイグレーション (今 回の目玉)
 品質を維持するために...
 ● 4⃣ (プロ管) マニュアルテストを複数回実施 
 ● 5⃣ (プロ管) デザイナーとのペアデザイン 
 ● 8⃣ (技術) デザインコンポーネントの破損はVRT で検査
 ● 9⃣ (技術) E2Eテストで重要・頻出導線の破損を 防ぐ


Slide 21

Slide 21 text

1⃣ スイッチングコストを極限まで減らす
 誰かが一気に対応する


Slide 22

Slide 22 text

1⃣ スイッチングコストを極限まで減らす
 以前、Flyleでは、リファクタリングタスクを開発作業のうちの20%を使用する手法を採用し ていました。(Vue2 から Vue3 へのアップグレードの初期はこの方法を採用していまし た。)
 しかし、自分を含め進捗が芳しくなく、その20%の時間を1人に集約するアプローチに変 更したところ、一気に進捗した、という経験をしました。
 これは、複数のタスクに同時期に取り組んだことにより、毎回それぞれのタスクの情報を 思い出すのに時間がかかっていたことが原因であると結論づけました。
 以降、Flyleでは、タスクスイッチングのコストを最小化する方法 (つまりインクリメンタル アプローチであろうがビッグバンアプローチであろうが誰かが一気に対応する方法) でリ ファクタリングを実施しています。


Slide 23

Slide 23 text

2⃣ 重要でないページの細かいデザイン調整は後回しに
 重要なことにフォーカスする


Slide 24

Slide 24 text

3⃣ 進捗は数値で見える化
 「重要でないページの細かいデザイン調整は後回しにする」とはいえ、当然できる限り多 くのページのデザインを細かく詰めた状態でリリースしたいわけです。
 これを調整するために、進捗は全て数値化して管理しました。
 具体的には、各ページのルートコンポーネントを行数と共に一覧化しました。
 一覧化した上で、毎日の目標進捗を掲げ、毎日の朝会で全員で確認しました。(これによ り、各メンバーも日々の目標が明確になるわけです)
 また、進捗に応じて、先回りしてデザイナーチームに、追加のページの詳細なデザインを 依頼できるわけです。これにより、定められた期間内でできるだけ多くのページのデザイ ンを詳細まで詰めることができました。


Slide 25

Slide 25 text

4⃣ マニュアルテストを複数回実施
 お客様にご迷惑をおかけしない


Slide 26

Slide 26 text

4⃣ マニュアルテストを複数回実施
 Big Bang リリースで恐ろしいことは、バグの発生です。漸進的リファクタリングと比べ、バ グ混入のリスクは圧倒的に高くなります。
 バグを全て潰し込むことは現実的ではないでしょう。
 しかし、一方で、結局のところお客様が日々お使い頂く上で不便がない状態であれば、 それは実質的にバグがない状態とも言える訳です。
 そこで、我々は、機能の詳細な一覧とユーザー権限のマトリクス表を作り、全社員に協 力頂いて、複数回のマニュアルテストを実施しました。
 これにより、結果として、リリース後、お客様からの報告を受けたバグはほとんどなかっ たと記憶しています。


Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

5⃣ デザイナーとのペアデザイン
 とはいえ細部にもこだわる


Slide 29

Slide 29 text

5⃣ デザイナーとのペアデザイン
 リリース直前の最終段階では、デザイン仕切れなかった細かな部分に関して、デザイナー1人 対 エンジニア2人 などのペアで、口頭ベースでペアデザインを実施しました。 
 このフェーズでは、デザインでは表現しきれていなかった細かな部分などをデザイナーが事前に リストアップした上で、ペアデザイン時にまずエンジニアに説明、エンジニアは少ない工数で対応 する方法や代替案を提案し、その場で合意しながら実装まで行う、というプロセスを実施しまし た。
 これにより、特に主要導線のデザイン少ない工数で一気に磨き込むことができました。 
 振り返ると、短期で取り組む Big Bang タイプのデザイン変更プロジェクトにおいては、この作業 は最後に品質を磨き込む重要なプロセスだったと思います。 


Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

技術面での工夫


Slide 32

Slide 32 text

我々が重要視したこと
 
 ● 最短期間でリリースする
 ○ 特にクリティカルなバグが見つかった時に対応できない期間を最小化するため 
 最短期間でリリースするために...
 ● 1⃣ (プロ管) スイッチングコストを極限まで減らす 
 ● 2⃣ (プロ管) 重要でないページの細かいデザイン 調整は後回しに
 ○ マージン・パディングなど... 
 ● 3⃣ (プロ管) 進捗は数値で見える化 
 ● 6⃣ (技術) ESLintのカスタムルールでレビューは できる限り自動化
 ● 7⃣ (技術) 可能な限り自動マイグレーション (今 回の目玉)
 品質を維持するために...
 ● 4⃣ (プロ管) マニュアルテストを複数回実施 
 ● 5⃣ (プロ管) デザイナーとのペアデザイン 
 ● 8⃣ (技術) デザインコンポーネントの破損はVRT で検査
 ● 9⃣ (技術) E2Eテストで重要・頻出導線の破損を 防ぐ


Slide 33

Slide 33 text

6⃣ ESLintのカスタムルールでレビューはできる限り自動化
 例えば、今回のデザインシステムプロジェクトでは、文字のタイポグラフィーを定義しまし た。これを全員が守るために、テキストは必ず UIコンポーネントでラップすることにしまし た。但しこれをレビューで手作業で見るのは大変です。それで独自の ESLint プラグイン を実装しました。また、UIコンポーネントにクラスを指定できると台無しなので、これを防 ぐルールも実装しました。


Slide 34

Slide 34 text

6⃣ ESLintのカスタムルールでレビューはできる限り自動化
 同様に Stylelint のカスタムルールも実装しました。
 これにより、デザインシステムの破壊を防ぎます。


Slide 35

Slide 35 text

8⃣ デザインコンポーネントの破損はVRTで検査
 Storybook と Chromatic を使用してUIコンポーネントに意図しない変更が入っていない かを自動チェックできる仕組みを導入しました。
 (これは余談ですが、Storybook でストーリーを for 文で一気に作成する方法がわからなかったので 独自の方法で for 文で作成できるようにしました。一般的にはどうするのが良いのでしょうか?) 


Slide 36

Slide 36 text

一瞬だけ Chromatic を見てみましょう


Slide 37

Slide 37 text

9⃣ E2Eテストで重要・頻出導線の破損を防ぐ
 Flyleは元々、Autify を使用して、重要導線 (例: ログイン) に対してはE2Eテストを実装し ていました。
 今回のリファクタリングプロジェクトにおいても、Autifyが意図しない破損がないかを確認 することで、重要導線が壊れていないかを確認することができました。
 (実際にはリファクタリング後にほぼ全てのAutifyのテストが落ちたのでその修正が大変 でしたが、それでも主要導線が動くことが確認できたことは重要だったと思います)


Slide 38

Slide 38 text

やった方がよかったこと (反省)


Slide 39

Slide 39 text

コンポーネント間の依存を考慮した作業順の決定
 今回のプロジェクトでは、単に規模の小さいページから順番に着手するというプロセスで 実施しました。
 小さいページから着手することで作業に慣れるためです。
 しかし、中盤から、作業のコンフリクトが何度か発生しました。ページA と ページB の両 方から使用されているコンポーネントを同時に修正してしまった、のようなケースです。
 これを防ぐためには、事前に依存ツリーを整理しておき、作業順序を決めると共に、ペー ジだけでなくコンポーネントの進捗も管理しておけば防げたと感じています。


Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

ここからが今回の目玉


Slide 42

Slide 42 text

7⃣ 可能な限り自動マイグレーション
 デザインシステム導入を含む多くのリファクタリング作業は単純作業の積み重ねです。
 単純作業はできる限り自動化できた方が良いと思います。
 ここからは、リファクタリングをできる限り自動化するテクニックを共有します。


Slide 43

Slide 43 text

マイグレーション手法
 マイグレーションを自動化する方法は主に2種類あります。
 ● 正規表現ベースでの置換
 ● ASTを変更することによる置換
 


Slide 44

Slide 44 text

正規表現ベースでの置換
 手軽にできる方法ですね。手軽にできる一方で、意図しない置換が発生する可能性が あります。
 例えば、HTMLのID属性を一括で削除したいとします。
 正規表現は /\sid=["'].*?["']/ といったところでしょうか。
 しかし、これでは const foo = (bar, id="foo") => {} のような文字列にもマッチしてしまい ます。
 これでは正しくリファクタリングができません。
 そこで、より高度なリファクタリングをするためには、ASTを変更することによる置換を選 択する場合があります。


Slide 45

Slide 45 text

{
 type: 'Program',
 body: [
 {
 type: 'VariableDeclaration' ,
 declarations: [
 {
 type: 'VariableDeclarator' ,
 id: {
 type: 'Identifier',
 name: 'foo',
 },
 init: {
 type: 'Literal',
 value: 'bar',
 raw: '"bar"',
 },
 },
 ],
 kind: 'const',
 },
 ],
 sourceType: 'module',
 }
 ASTを変更することによる置換
 AST (Abstract Syntax Tree) とは、抽象 構文木のことです。
 例えば、const foo = "bar"; は、
 右のようなASTになります。


Slide 46

Slide 46 text

{
 type: 'Program',
 body: [
 {
 type: 'VariableDeclaration' ,
 declarations: [
 {
 type: 'VariableDeclarator' ,
 id: {
 type: 'Identifier',
 name: 'foo',
 },
 init: {
 type: 'Literal',
 value: 'baz',
 raw: '"baz"',
 },
 },
 ],
 kind: 'const',
 },
 ],
 sourceType: 'module',
 }
 ASTを変更することによる置換
 “bar” を “baz” に変更する 
 {
 type: 'Program',
 body: [
 {
 type: 'VariableDeclaration' ,
 declarations: [
 {
 type: 'VariableDeclarator' ,
 id: {
 type: 'Identifier',
 name: 'foo',
 },
 init: {
 type: 'Literal',
 value: 'bar',
 raw: '"bar"',
 },
 },
 ],
 kind: 'const',
 },
 ],
 sourceType: 'module',
 }


Slide 47

Slide 47 text

ASTを変更することによる置換
 
 JavaScript の場合、ASTはJSON構造 (Record型) として表現されることが多いため、仕 組みさえわかってしまえば、後は普段のプログラミングと同様に、JSONオブジェクトを変 更すれば良いですね。
 
 しかし、ASTの構造はどうやって理解すれば良いでしょうか?


Slide 48

Slide 48 text

ASTの構造を理解する方法
 AST Explorer (https://astexplorer.net/) というサイトがあります。
 このサイトでは、HTML, JavaScript, CSS をはじめとし た多くのプログラム言語に関して、ASTを確認できるサ イトです。
 例えば、JavaScriptの場合、Acorn や Espree など多く のパーサーを使用してASTを確認できます。 
 また、ESTree という JavaScript の AST 仕様は、 ESTreeのGitHubリポジトリで確認できます。 
 https://github.com/estree/estree/blob/0fa6c005fa45 2f1f970b3923d5faa38178906d08/es5.md 


Slide 49

Slide 49 text

VueのファイルをASTに変換する方法
 各プログラム言語に対するASTの確認方法はわかったと思います。
 しかし、Vueファイルには HTML, TypeScript, SCSS など、複数の言語を実装します。どう やってVueファイルの内容をASTに変換すれば良いのでしょうか。
 Vueの内部実装はわからないのですが、Svelteの場合、正規表現を用いて ブ ロックと <style> ブロックを判定しています。
 ということで、正規表現で各ブロックを分割しましょう。
 const REGEXP_TEMPLATE = /(<(template).*?>)([\s\S]*)(<\/(template)>)/m;
 const REGEXP_SCRIPT_STYLE = /(<(script|style).*?>)([\s\S]*?)(<\/(script|style)>)/m;


Slide 50

Slide 50 text

VueのファイルをASTに変換する方法
 各ブロックに分解できたら、プログラムをASTにしていきましょう。
 例えば JavaScript を recast を使用して AST にする最小のコードは以下です。
 
 import { parse } from "recast";
 const ast = parse("const foo = 'bar';");
 


Slide 51

Slide 51 text

VueのファイルをASTに変換する方法
 得られたASTを走査して変更を加えるコードの雛形は以下です。
 
 import { walk } from "zimmerframe";
 walk(ast, null, {
 _(node, { state, next }) {
 // AST に変更を加える処理 
 manipulate(node);
 // 次のノードへ
 next(state);
 },
 });


Slide 52

Slide 52 text

VueのファイルをASTに変換する方法
 具体的なASTを変更するコードは以下です。
 
 import type { Node } from "@babel/types/lib";
 const manipulate = (node: Node) => {
 if (node.type === "VariableDeclarator") {
 const { init } = node;
 if (init?.type === "StringLiteral") {
 init.value = "bar";
 }
 }
 };


Slide 53

Slide 53 text

VueのファイルをASTに変換する方法
 また MagicString を使用して書き換えることもできます。
 import type { Node } from "@babel/types/lib";
 import MagicString from "magic-string";
 
 const magicString = new MagicString("const foo = 'foo';");
 const manipulate = (node: Node) => {
 if (node.type === "VariableDeclarator" ) {
 const { init } = node;
 if (init?.type === "StringLiteral") {
 const [start, end] = init.range ?? [];
 if (start != null && end != null) {
 magicString.overwrite(start, end, "'bar'");
 }
 }
 }
 };


Slide 54

Slide 54 text

MagicString とは
 Suppose you have some source code. You want to make some light modifications to it - replacing a few characters here and there, wrapping it with a header and footer, etc - and ideally you'd like to generate a source map at the end of it. You've thought about using something like recast (which allows you to generate an AST from some JavaScript, manipulate it, and reprint it with a sourcemap without losing your comments and formatting), but it seems like overkill for your needs (or maybe the source code isn't JavaScript). 
 
 Your requirements are, frankly, rather niche. But they're requirements that I also have, and for which I made magic-string. It's a small, fast utility for manipulating strings and generating sourcemaps. 


Slide 55

Slide 55 text

MagicString とは
 ソースコードがあるとする。あちこちの文字を置き換えたり、ヘッダーとフッターで囲んだりするなど、軽い修正を 加えたい。理想的には、最後にソース・マップを生成したい。あなたはrecast(JavaScriptからASTを生成し、それ を操作して、コメントや書式を失うことなくソースマップと一緒に再印刷できる)のようなものを使うことを考えまし たが、それはあなたのニーズ(あるいはソースコードがJavaScriptでないかもしれない)には過剰なことのように 思えます。
 
 あなたの要求は、率直に言って、かなりニッチです。しかし、それは私も持っている要件であり、そのために magic-stringを作ったのだ。これは、文字列を操作してソースマップを生成するための、小さくて高速なユーティ リティだ。


Slide 56

Slide 56 text

VueのファイルをASTに変換する方法
 最後に、変更後のASTを文字列に書き戻す最小のコードは以下です。
 
 import { print } from "recast";
 const jsString = print(ast).code


Slide 57

Slide 57 text

VueのファイルをASTに変換する方法
 
 同じことを HTML や CSS にも適用することで、Vueファイルを自動でリファクタリングする ことができますね。


Slide 58

Slide 58 text

VueのファイルをASTに変換する方法
 
 とはいえ、実際に使えるレベルのコードを1から書くのは面倒だと思います。
 そこで、今回Flyleで実際に使用したコードをベースにしたリファクタリングツールをライブラリと して公開しました。 vue-service-bay というライブラリです。
 https://github.com/flyle-io/vue-service-bay
 
 README に使い方を書いていますので、このライブラリが皆様のリファクタリングライフを劇的 に楽にして、機能開発に専念できる環境づくりに寄与できたらとても嬉しく思います。
 また、よければスターして頂けると嬉しいです。
 弊社開発チームのメンバーも喜ぶと思います。


Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

想定質問 : なぜマイグレーションをコード化する必要があるのか
 正規表現を使用して確認しながら一括置換すれば良いじゃないかと思われるかもしれま せん。しかし、この方法だと2つの問題があります
 ● 前のスライドで述べたように誤検知が発生します。
 ● 実際のプロジェクトでは色んな人が並行で開発しているため、全員の開発のキリが 良いタイミングで一括でマイグレーションする必要があります。
 都度都度確認しながらマイグレーションする場合、その間他の全員が待ちになって しまうため、事前にマイグレーションをコード化しておいて、ある時点で一気に置換 する必要があります。


Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

まとめ
 ● Big Bang リリースを避けられない場合もある
 ○ それでも可能な限りスコープは最小化しよう
 ○ そして段取りが命
 ● 自動化できるものは全て自動化しよう
 ○ Big Bang の期間はバグ修正すらできないのでリスクが高まっていく
 ○ 機械は人間よりも確実で早い
 ○ vue-service-bay もよければ使ってください
 ● ペアデザインはおすすめ
 ○ 事前に決まっていない細かなデザインは最後にデザイナー・エンジニア間のペア作 業で調整するのがおすすめ。
 ● マニュアルテストは何度もやろう
 ○ お客様にご迷惑をおかけしないために


Slide 63

Slide 63 text

デザインシステムを導入した個人的な感想
 ● CSSあまり書かなくて良いの最高 (単純にコードが見やすい)
 
 ● デザインシステムというレールがあるからこそESLint / Stylelint でより縛れるから 最高
 ○ よりコードの統一感を維持できる
 ○ コードレビューも楽になった
 
 ● デザイナー間とのコミュニケーションが簡単になって最高
 ○ デザインシステムと乖離するデザインがあったら単に相談すればよくなった。 そしたらデザインチームで検討してデザイン原則・デザインシステムに適合し たUIに変更してくれる
 ■ (以前は一旦ここはそれで実装するか、みたいな意思決定が多少あったのでコードがぐちゃっ とする原因になっていた) 


Slide 64

Slide 64 text

ところで、DALL·E 3 で描いていない画像がどれか、皆さんわかりましたか?


Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

おわり