Slide 1

Slide 1 text

1 CSS Architecture on Vue.js Vue.jsのScoped CSSに適したCSS設計を考える ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ

Slide 2

Slide 2 text

2 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. ⾃⼰紹介 Takami Yamada, aka @tacamy Design Engineer HTML / CSS : 10年 9⽉に株式会社プレイドに転職しました :tada: Vue.js : 2年 39

Slide 3

Slide 3 text

3 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. KARTEについて ֬ೝ͢Δ جຊαʔϏε΍͝ར༻ํ๏Λ͝঺հ͠·͢ɻ ͓ಘͳಛయ΍໾ཱͭ৘ใ͕ຬࡌͰ͢ɻ ॳΊͯͷํ΁ ְתּׅ然钠ׅ׷ 6*رؠ؎ش٦׌ֽוזַזַ♳麦׃זְծ➙ ״׶إٔؗ،حف׃׋ְהְֲ倯䗳铣דׅկ UIرؠ؎ش٦ָ濼׏גֶֻץֹ 7אךرؠ؎ٕٝ٦ٕ DESIGN RULES » CHECK ׆׏ה⢪ִ׷،؎ذي׌ֽ׾䲧ִת׃׋կ ֿך堣⠓׾ֶ鋅鷕׃זֻկ 窫㼎ծ 妜׃ְ ౙͷओ໾ɺ Ξ΢λʔ COLLECTION OUTER 嗚稊勴⟝׾㼰׃㢌刿ׅ׷׌ֽדծ֮ז׋ך椚 䟝ך暟⟝ח⳿⠓ִ׷〳腉䚍ָ넝ֻז׶תׅկ 勴⟝׾㢌ִג嗚稊׃ג׫גֻ׌ְׁկ ׀䋞劄ך暟⟝כ 鋅אַ׶תׇ׿ד׃׋ַ ꟗׄ׷ ⼀⼈⼀⼈に合わせた 顧客体験を提供 Webサイトの訪問者の⾏動を 顧客ごとにリアルタイムに解析 $9 ސ٬ମݧ ϓϥοτϑΥʔϜ

Slide 4

Slide 4 text

4 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 対象 こんな⼈向けの話です • CSS設計は得意だが、Vue.jsのScoped CSSでの設計⽅法に迷いがある… • Scoped CSSなら安⼼!と適当に書いたらスタイルがバッティングしてハマる…

Slide 5

Slide 5 text

5 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. VueコンポーネントのScoped CSS コンポーネントってなんだろう?

Slide 6

Slide 6 text

6 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. VueコンポーネントのScoped CSS ひとまとまりの機能を持つ ⾃⼰完結型の再利⽤可能な部品 機能や振る舞いがワンセット どこで利⽤しても同じように動作

Slide 7

Slide 7 text

VueコンポーネントのScoped CSS 7 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ Vue.jsのSFCは 1つのファイルで コンポーネントを実現 // ίϯϙʔωϯτΛߏ੒͢Δཁૉ
// ίϯϙʔωϯτͷঢ়ଶ΍ػೳΛఆٛ // ίϯϙʔωϯτͷݟͨ໨Λ੍ޚ .MyComponent { /* ... */ } コンポーネント単位のスコープでCSSを書ける

Slide 8

Slide 8 text

8 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. VueコンポーネントのScoped CSS 念願のCSSのスコープを⼿に⼊れた

Slide 9

Slide 9 text

VueコンポーネントのScoped CSS 9 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ でも実際はただのCSS // Example.vue
hi
.Example { color: red; } // ੜ੒͞ΕΔHTML .Example[data-v-f3f3eg9] { color: red; }
hi
属性セレクタによりScopedを擬似的に実現 コンポーネント単位で⾃動的に付与される

Slide 10

Slide 10 text

10 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. VueコンポーネントのScoped CSS そのため、本物のスコープを持つ Shadow DOMとは異なる

Slide 11

Slide 11 text

11 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. Scoped CSSの留意点 Vue.jsのScoped CSSの留意点 https://vue-loader-v14.vuejs.org/ja/features/scoped-css.html

Slide 12

Slide 12 text

Scoped CSSの留意点 12 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ 要素セレクタへの スタイル指定はNG // NG

hi

p { color: red; } // OK

hi

.Example { color: red; }

Slide 13

Slide 13 text

13 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. Scoped CSSの留意点 • 要素セレクタへのスタイル指定は、属性セレクタと組み合わせたとき何倍も遅くなる • クラスセレクタに対するスタイル指定なら、パフォーマンスに影響なし

Slide 14

Slide 14 text

Scoped CSSの留意点 14 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ⼦コンポーネントの ルート要素は 親スコープの影響をうける // Parent.vue
.Container { color: red; } // Child.vue
...
... // ੜ੒͞ΕΔHTML
...
⼦コンポーネントの⽂字⾊もredになる

Slide 15

Slide 15 text

15 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. Scoped CSSの留意点 • ⼦のルート要素には、⼦⾃⾝のデータ属性+親のデータ属性が付与される - 親のScoped CSS内で、⼦のルート要素と同じクラス名がある場合、 ⼦にそのスタイルが適⽤される 親はレイアウト⽬的で⼦のルート要素をスタイリングできる - コンポーネント⾃体にはmarginを持たせず、親コンポーネントでmarginをつける等 意図せぬスタイルのバッティング • 親と⼦で同じクラス名がなければ問題ない

Slide 16

Slide 16 text

Scoped CSSの留意点 16 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ deepセレクタで ⼦コンポーネント以下に 影響を与えることが可能 // VueϑΝΠϧ .Parent >>> .Child { /* ... */ } // ίϯύΠϧ݁Ռ .Parent[data-v-f3f3eg9] .Child { /* ... */ } スコープが消え去る

Slide 17

Slide 17 text

17 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. Scoped CSSの留意点 • deepはセレクタのネストにより優先度が⾼くなり、⼦のスタイルが負ける場合もある • Scopedも崩壊するので極⼒つかわない • Element UIなどの外部ライブラリのスタイル調整にはテーマ機能を利⽤ ⾜りないスタイルは専⽤のラッパーコンポーネントを⽤いて影響範囲を最⼩化

Slide 18

Slide 18 text

18 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. Scoped CSSの留意点 これらの特徴によるトラブルを 回避する⽅法

Slide 19

Slide 19 text

19 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. Scoped CSSの留意点 命名規則に則って 要素に固有のクラス名を付与

Slide 20

Slide 20 text

20 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. Scoped CSSの留意点 BEMの考え⽅に似ている…?

Slide 21

Slide 21 text

21 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. BEM的Scoped CSS設計 コンポーネントを BEMのBlockのように捉えてみる

Slide 22

Slide 22 text

22 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. BEM的Scoped CSS設計 .block .ComponentName • BEMのblockの粒度 ≒ Vueのコンポーネントの粒度 と捉える • コンポーネントのルート要素がblockにあたる

Slide 23

Slide 23 text

23 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. BEM的Scoped CSS設計 .block__element .ComponentName__element • block部分がComponentNameに変わるだけで、他はBEMと同様 • .ComponentName__element__element にならないよう - 粒度が⼤きすぎる ≒ 機能も複雑になりやすい • 詳細度を⼀定に保つため、CSSは1階層で記述

Slide 24

Slide 24 text

24 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. BEM的Scoped CSS設計 .block__element--modifire .ComponentName__element._modifire • modifireは状態によってtemplate内で付け外しすることが多いため、 BEM式はさすがに冗⻑で⾒づらくなってしまう • blockやelementと組み合わせてスタイル指定することを前提に _modifireの形式とする - 組み合わせることで、blockやelementより詳細度が⾼くなるメリットも • modifireはJSで扱うことが多いため、ハイフンよりアンダーバーの⽅が楽

Slide 25

Slide 25 text

25 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. BEM的Scoped CSS設計 BEM式で命名するメリット • 広く知られている命名ルールで共通認識があり、書き⽅に統⼀性が⽣まれる • コンポーネント内のCSSに秩序が⽣まれる • HTMLのクラス名を⾒るだけで、どこにスタイルが書かれているか分かる • あとからコンポーネントを分割するときも、クラス名がバッティングしない

Slide 26

Slide 26 text

26 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. BEM的Scoped CSS設計 でもBEMはクラス名が⻑くて⾯倒?

Slide 27

Slide 27 text

BEM的Scoped CSS設計 27 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ $options.nameと Sassの&で楽
...
export default { name: 'Example' }; .Example { &__element { /* ... */ } }

Slide 28

Slide 28 text

28 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネント名の命名規則 コンポーネント名の命名規則 https://jp.vuejs.org/v2/style-guide/index.html Vue.jsのスタイルガイド

Slide 29

Slide 29 text

29 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネント名の命名規則 2単語以上で構成 • HTML要素との衝突を防⽌ - HTMLでは⼤⽂字も⼩⽂字扱いになるため はとなり、HTML要素とバッティングする - 将来追加される未知のHTML要素のことも考慮

Slide 30

Slide 30 text

コンポーネント名の命名規則 30 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ 密結合コンポーネントの名前 // NG components/ |- TodoList/ |- Item/ |- index.vue |- Button.vue |- index.vue // OK components/ |- TodoList.vue |- TodoListItem.vue |- TodoListItemButton.vue • 親コンポーネントと密結合した⼦コンポー ネントは、親コンポーネントの名前をプレ フィックスとして含む • 親コンポーネントのディレクトリの中に⼦ コンポーネントを⼊れるのは⾮推奨 - 同じような名前のファイルが多数できてしまい、 エディタ上でのファイル切り替えが難しくなる - ネストが深くなると、エディタのサイドバーでコン ポーネントを参照するのに不便

Slide 31

Slide 31 text

31 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. ディレクトリ構成 ディレクトリ構成

Slide 32

Slide 32 text

ディレクトリ構成 32 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ // ྫͱͯ͠Nuxt.jsͷߏ଄Λ΋ͱʹ // ελΠϧʹؔ࿈͢Δ෦෼Λൈਮ project/ |- assets/ |- scss/ |- _mixins.scss |- _variables.scss |- images/ |- components/ |- lauouts/ |- default.vue |- pages/ |- index.vue |- directory/ |- index.vue • mixin.scssとvariables.scssは components、layouts、pages以下 すべてのコンポーネントから読み込む • layoutsは共通レイアウト • pagesはルーティング • 共通コンポーネントはcomponentsに ここの分類を考える

Slide 33

Slide 33 text

33 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 コンポーネントの分類

Slide 34

Slide 34 text

34 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 コンポーネントの分類 • 複雑すぎる分類はコストがかかる - 所属するコンポーネントを議論するためのコスト - コンポーネント利⽤時の探すコスト - 正しい分類を維持していくための定期的な⾒直しコスト - 新メンバー⽴ち上がりまでのコスト • 何のために分類するのか⽬的を明確に

Slide 35

Slide 35 text

35 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 コンポーネントの粒度(⼤きさ) • 階層が深くなりすぎると、propsバケツリレーと$emit地獄に • 細かすぎる分割はパフォーマンスにも影響あり

Slide 36

Slide 36 text

36 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 粒度による分類 Part / Module • Partコンポーネント - 機能が成⽴する最⼩単位の部品 - 単体でも利⽤可能 - ボタン、フォームパーツ、アイコン • Moduleコンポーネント - Partsを組み合わせて別の機能を持つ - Moduleに別Moduleも含めることもできる - カード、検索ボックス、ヘッダー

Slide 37

Slide 37 text

37 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 役割による分類 Presentational / Container https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 • Presentationalコンポーネント - 再利⽤される前提の要素 - 親からpropsを受け取って表⽰ - 親へ$emitで伝達 - storeに依存しない • Containerコンポーネント - ページそのもの、またはページを構成する要素 - ⼦へのデータの受け渡し - ⼦からイベントを受け取って処理を⾏う - storeのデータの取得や更新を⾏う

Slide 38

Slide 38 text

38 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 プロジェクトに最適な分類とは? • 最初はなるべくシンプルにはじめて、必要に応じて分類を増やすとよさそう • 議論に時間がかかる場合は、そもそもその分類がToo muchかも?

Slide 39

Slide 39 text

39 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 分類しないですべてのコンポーネントを同⼀階層に 1st STEP

Slide 40

Slide 40 text

40 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 分類(ディレクトリ) コンポーネントの例 parts ボタン、フォームパーツ、アイコン modules カード、検索ボックス、ヘッダー 共通コンポーネントが増えてきた 探しやすさのために粒度で分類 2nd STEP

Slide 41

Slide 41 text

41 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. コンポーネントの分類 分類(ディレクトリ) 役割 Store依存 parts Presentational × modules Presentational × container Container ○ データの流れが追えなくなってきてデバッグしづらい 役割の明確化のために分類 3rd STEP

Slide 42

Slide 42 text

42 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 理想と現実

Slide 43

Slide 43 text

43 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 これまでの話は理想であり 最初から理想どおりにつくるのは難しい すべてのCSSをコンポーネントに紐づくScoped CSSにするメリット • 構造に⼀貫性があり、保守しやすい(⻑期運⽤にも耐えうる) 実現するための課題 • 初期段階で、すべてのユースケースを考慮したコンポーネントをつくるのは困難 • スタートアップのプロトタイピング的なプロジェクトでは、時間をかけてつくっても無駄になる可能性 • 調整⽤クラスはないほうがいいけど、利⽤者側からするとあった⽅がやっぱり便利

Slide 44

Slide 44 text

44 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 徐々に導⼊する コンポーネント的CSS設計

Slide 45

Slide 45 text

45 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 すべてのスタイルを外部CSSファイルに記述 templateのHTMLにクラスを付与するだけでスタイルを適⽤できる デザイナーがCSSを書くときに、Vueコンポーネントの粒度を意識しなくてよい 記述されたスタイルがどこで利⽤されているかわからない - 不要なスタイルが残り続けることで負債が蓄積し、メンテナンスコスト⾼ - 修正したつもりが意図しない箇所が壊れてしまう、エンジニアは触りたくない CSSを書かないエンジニアも、状態によるクラスの付け替えルールを把握する必要がある 1st STEP

Slide 46

Slide 46 text

46 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 • 共通コンポーネントのスタイルが書かれた外部CSSを全ページで読み込む • それ以外のスタイルは、各コンポーネントにScoped CSSで記述 外部CSSファイルとScoped CSSのハイブリッド式 2nd STEP KARTEはイマココ!

Slide 47

Slide 47 text

47 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 ハイブリッド式のメリット・デメリット スピード感を維持したまま、⼀部でScoped CSSの恩恵を受けられる Scoped内なら安⼼して、誰でもCSSを触ることができる スタイルの適⽤元がわからなくなるときがある • 共通コンポーネントに接頭辞をつける命名ルールである程度回避 Scoped内を⾃由に書けすぎてしまう • UIのルールが守られていない箇所もある • 同じような機能やスタイルがコピペであちこちに散在(でも微妙に違ったり) コンポーネントが外部のCSSに依存しているので、CSSが古いページにコンポーネントを 配置したときに表⽰が崩れてしまう

Slide 48

Slide 48 text

48 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 しばらく運⽤して、パターンがある程度出揃った段階で すべてのスタイルをScoped CSSでカプセル化 どこで使⽤しても同じように表⽰できることが保証できる スタイルの影響範囲が明確で、⻑期的に保守しやすい コンポーネントのアップデートの周知がうまくされないと 似たようなコンポーネントがつくられてしまう 細かいコンポーネントが増えるとテストの⼿間も増える 3rd STEP

Slide 49

Slide 49 text

49 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. 理想と現実 どの⽅法を選択するか? • 唯⼀の正しい⽅法は存在しない • プロジェクトの規模・フェーズ・個々の事情による • 外部のCSSに依存する場合も、 リセットやベーススタイルは極⼒ブラウザデフォルトに合わせておくと吉

Slide 50

Slide 50 text

まとめ 50 ɹɹʛɹɹ© 2019 PLAID Inc. 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ より良いCSSのゴール ≒ コンポーネント 予測しやすい 再利⽤しやすい 保守しやすい 拡張しやすい

Slide 51

Slide 51 text

51 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ ɹɹʛɹɹ© 2019 PLAID Inc. まとめ CSS設計 コンポーネント設計

Slide 52

Slide 52 text

ɹɹʛɹɹ© 2019 PLAID Inc. 52 2019.11.26 ʛ Mercari x Merpay Frontend Tech Talk vol.3 ʛɹ 株式会社プレイドについて 株式会社プレイド 東京都中央区銀座6-10-1 GINZA SIX 10F 設⽴:2011年10⽉ 従業員:130名 資本⾦:1億円 ※資本準備⾦含む 株式会社プレイドでは KARTEのコンポーネント化を ⼀緒に推し進めてくれる CSS設計の得意な仲間を募集中です! https://www.wantedly.com/projects/299653

Slide 53

Slide 53 text

No content