こんにちは。Wantedly でフロントエンドエンジニアをしている 原 剛士 ( @chloe463 )です。UIの刷新に関連した取り組みについてのストーリーの続編として、本記事ではUI刷新の際の技術的な課題解決の取り組みについて紹介したいと思います。
UIの刷新についてはすでにエンジニア・デザイナーそれぞれから記事が公開されていますので、そちらも合わせて読んでいただけると幸いです。
リニューアルではなく「リノベ」 今回のプロジェクトは社内では「リニューアル」ではなく「リノベ」と呼んでいました。これまで社内で「リニューアル」と呼んだ場合、フロントエンドのコードベースを1から作り直し、バックエンドも新しいマイクロサービスに切り出すといった大掛かりなプロジェクトのことを指していました。また技術面以外にも機能や体験を大きく変えるということも含みました。
2年前に行った募集作成画面、スカウト画面のリニューアルでも既存のコードベースから離れ、新規のコードで開発を行い、GraphQL を Backend for Frontend (BFF) に据えたアーキテクチャの導入、新規マイクロサービスの開発など大幅な技術刷新を行いました。社内ではこの技術スタックをフロントエンド v4 と呼んでいます。(v1~v4 技術スタックの世代の説明は後述します。)
フロントエンド v4 は基本的にバックエンドにマイクロサービスを持ち、その前段に BFF として GraphQL サーバーを置き、フロントエンドは GraphQL サーバーを経由してデータの送受信を行うというアーキテクチャを取っています。GraphQL 活用の利点の一つに、スキーマ定義を用いて型安全な開発ができる点があります。以前ご紹介したとおり、Wantedly では Apollo の機能を用いてバックエンドからフロントエンドまで型定義を浸透させることで型安全な開発をしています。しかしこの型定義の作成はドメインモデルの設計からのやり直しをする必要があり非常にコストがかかります。募集作成画面、スカウト画面のリニューアルの際にもこの型定義に数週間〜1ヶ月程度の時間を要しました。
今回のリノベプロジェクトでは採用管理画面だけでも大きく7画面+それに紐づく複数の小画面が対象であったため、それぞれで定義のし直しをしていると数ヶ月から年単位の時間がかかってしまいます。しかし今回のリノベプロジェクトではスピード重視で、3ヶ月から半年の間に完了させたいという目標があったため、既存のバックエンドアーキテクチャには手をいれず、フロントエンドのみに手を入れる形でUIの刷新を完遂するということになりました。つまり全く新しいコードベース上に作るのではなく、既存のコードベース上で開発することになったため、リニューアルではなく「リノベ」という呼び方をしています。
デザインシステムの適用 先述したとおり、Wantedly のフロントエンドには技術スタックが4世代あります。社内では v1〜4 と呼んでいます。
v1: Rails view (Haml, erb, CoffeScript, jQuery, Backbone.js ...) v2: React with JavaScript / Redux v3: React with TypeScript / Redux / Server Side Rendering v4: React with TypeScript / GraphQL / Server Side Rendering 今回のリノベ対象は長らく手を入れてこなったページということで必然的に古い世代のコードになります。つまり v1, v2 の部分です。リノベ関連のリリースノートやストーリーでもあるように今回のプロジェクトで全体にデザインシステムが適用されることになりましたが、下記のような課題がありました。
v1 スタックにおいてはデザインシステムの標準実装が存在しない デザインシステムの React 実装は styled-components 使用を想定して開発されているが、v2 においては styled-components を使用していない これらの課題に対し、どう取り組んでいったか次の節で紹介していきます。また、デザインシステム整備についての取り組みはこちらのストーリーに詳しく記載してありますので、ぜひ読んでみてください。
v1へのデザインシステムの導入 v1 のコードベース、つまり基本的に Rails の view で書かれている部分のリノベは今回のプロジェクトで1番大変だった部分ではないかと思います(個人の感想です)。Wantedly の中でも最もレガシーなコード群が広がり、デザインシステム実装もなく苦労した部分でした。
デザインシステムの実装がしっかり開発されてきたのは、v4 開発とほぼ同時期であるため React 実装のライブラリが整備されているだけでした。v1 向けの HTML + CSS (+ vanilla js) のライブラリも他プロジェクト用に作成されたものが一応ありましたが、しっかりメンテナンスされていたわけではなく、必要なコンポーネントが揃っていない状態でした。そのため解決策として下記の3案で検討しました。(この記事を読んだ社員向け情報: wantedly/product-quality#93)
都度スタイルを定義する 開発リポジトリ内のデザインシステム実装を使う SCSS 版デザインシステムを使う 1案の都度スタイルを定義では、共通のデザインシステム実装を開発せずにアプリケーション上でデザインシステム準拠のコンポーネントが出てきた際に都度スタイルを定義する方式です。2では1よりもちょっと洗練したやり方で、都度定義ではなくアプリケーションコード内での共通スタイルを定義しようというものです。DRY原則に従うことで開発生産性やリポジトリ内の治安維持にも繋がります。3は更に進んで先述した v1 向けのデザインシステム実装をしっかり整備しようというものです。
内部で検討した結果、2案と3案の折衷案で進めることとなりました。判断理由は下記です。
1案では開発スピードはあまり落ちないかと考えましたが、一方でデザインシステムの理解度が高くないと実装が漏れていくスタイルがありそうだと考えられました。特にクリックアクションが伴うボタンやリンク、カード等のリアクション (色や box-shadow の変化) の実装が漏れがちです。 2案でリポジトリ内限定ではあるが、共通実装をもつことで上記の課題を解決できそうだと考えました。 また2案ではデザインシステム実装が外部のライブラリにならないため、試行錯誤が1番やりやすいというメリットがあります。 一方で、3案では今後の共通化につながる可能性はありますが、その恩恵を受けることができるプロジェクトが今回の対象となるページと、もう1プロジェクトしかありません。 さらに言えば、今後新規に作られるページは基本的には v4 ベースになっていくことを考えるといまこのタイミングで v1 のためのライブラリ整備は投資としてはよくない選択になります。 とはいえベーシックな部分 ―共通で使う色の定義、文字(font-family や大きさ)、 マウスオーバー時のリアクションの定義など― は共通で持っておいたほうがよさそうです。 以上より 「SCSS 版デザインシステム上でデザインシステムの Level 0: Foundation と呼ばれている基礎となるスタイルを実装し(3案)、Level 1 以上のある程度複雑性を伴うコンポーネントのスタイルをアプリケーションリポジトリ上で実装する(2案)」 という方針をとりました。
結果的にこの手法は成功したと思っています。開発生産性が落ちたように感じたことはなかったですし、特段開発時に困ったシチュエーションもありませんでした。
また、今回のプロジェクトではこれまでのメイン領域がフロントエンドではなかったエンジニアも関わりましたが、スタイル関連のコードレビュー指摘がデザインシステムの用語を使っていったことも副次的な効果としてよかったと思っています。 (e.g. ここのマウスオーバーリアクションはデザインシステムのここの定義を参考に作ってください。ここの色は blackAlpha50 だと思います。)
v2へのデザインシステムの導入 次に v2 領域の話です。先述のとおり、デザインシステムのReact実装はv3, v4の技術スタック(React + styled-component) を前提として作られています。しかしながら v2 ではReact + css-modules (SCSS) でスタイリングされていました。もちろんそのままでも導入は可能だとは思いますが、デザインシステム実装のスタイルをオーバーライドや拡張したくなった場合に、styled-components と scss 両方のご機嫌を取りながら実装するのは、開発中の生産性を下げる原因になりかねず、また後の余計な技術的負債を残すだけになる可能性が高いと考えました。また v3 以降の styled-components を使った開発を経験しているメンバーも多く、全体を styled-components を使ったほうがリノベ中・完了後の生産性が上がりそうだとも考えました。
それを実現するために、まず担当する画面の開発序盤ですべてのスタイルを SCSS から styled-components に書き換えるという作業を行おうと考えました。しかし、すべて手作業でやっていたらもちろん多大な時間もかかりますし、ミスが発生してしまう可能性もあります。そのために簡単なスクリプトを書いて可能な限り自動化を進めようと考えました。そのスクリプトがこちらです。
https://gist.github.com/chloe463/dede8e92dfcb6f43d08b45248fc853dc
もちろん自作する前に、同様のことができる OSS などないかと探したのですが、こういうユースケースがないのか、はたまたチャレンジしたがいいものが作れなかったのか...…。それらしいものを見つけられなかったため自分で書くことにしました。
このスクリプトに次のような SCSS ファイルを通すと、
$blackAlpha500: rgba(0, 0, 0, 0.56);
$blackAlpha700: rgba(0, 0, 0, 0.74);
.Base {
color: $blackAlpha500;
background-color: rgba(255, 255, 255, 0.8);
&:hover {
color: $blackAlpha700;
}
& > .A {
background-color: red;
}
}
下記の結果が出力されます。
const blackAlpha500 = "rgba(0, 0, 0, 0.56)";
const blackAlpha700 = "rgba(0, 0, 0, 0.74)";
const Base = styled.div`
color: ${blackAlpha500};
background-color: rgba(255, 255, 255, 0.8);
&:hover {
color: ${blackAlpha700};
}
& > .A {
background-color: red;
}
`
今回対象となったページの scss ファイルでは複雑な mixin や function の定義や使用があまりなかったため、ほとんど正規表現のみを使って変換ができました。すべて styled.div になってしまうのですが、そこは手作業でカバーしました。
このスクリプト開発時の個人的に重きを置いたコンセプトは1つ「完璧を目指さない」という点です。理由は下記です。
このスクリプトは2, 3日で不必要になる、使い捨てスクリプトであることが確実 ( というかこんな辛い作業が何度も何度も発生してほしくはない) 70-80%の大まかな変換をしてくれるだけでも大助かり このスクリプトをリッチに仕上げるよりももっと重要な作業が控えている このスクリプトを使って中くらいの大きさのコンポーネント1つで変換を試したところ、30分ほどで修正 PR が作成できました。対象ページでは 30 ほどのコンポーネントがあったため、大体15時間 (2日弱) ほどあればすべて書き換えられそうだという想定が立てられました。全行程の2日程度であれば許容できそうではありますし、むしろこれをやっておいたほうが後の開発生産性に対してポジティブな影響を及ぼすだろうと判断し、この作業を進めました。
実際には想定+αの 2.5 日程度で書き換えが完了しました。(途中ミーティング等もあったので、実働では2人日程度。) ただし PR 数が 30 程度になり、全てを細かくコードレビューしてもらうのは厳しいという状況になってしまいました。そのために動作ベースのレビューという形に方針転換し、エンジニア向けのテスト環境に2時間程度デプロイして、社内に呼びかけを行い、壊れている箇所がないかのチェック・既存のスタイルと乖離している部分がないかのチェックを行いました。チェックで洗い出した箇所を再度修正して master にマージすることにより、以後の開発がスムーズに進むようになりました。下のスクリーンショットはあるページを styled-components に置き換えたときのリリースブランチの様子です。
下図はチェック会で気づいたスタイル崩れや修正漏れの事項(一部)です。
別メンバーが担当した画面では最初に一気に進めるのではなく、リノベの開発を進めるのと同時に都度変換をしていったというところもありました。しかしながら、開発終了時の KPT において「先に styled-components 化を完了おいた方が、どのコンポーネントのスタイルがどういうスタイルに変わったかがわかりやすくなるため、レビューの観点からも先に進めておいたほうがよかった」という振り返りがあったため、この進め方はよかったと考えています。
また当初の目的であったデザインシステム実装の導入に関しても問題なく進み、自分を含めた v3 以降の開発経験メンバーの手もありスムーズに開発が進みました。
また、このスクリプトは別画面を対応した池田の手により改良が加わりよりよい結果を出力するスクリプトへと進化しました。css-modules の使用箇所の jsx を読むことにより、出力が div 以外の要素にも対応し、更に高速に書き換えが進みました。
最終的に企業側管理画面の v2 で作られているページのスタイルはすべて styled-components となりました。
こんにちは。20卒として入社しバックエンドエンジニアをしている 池田 伊織 ( @NotFounds8080 )です。リノベを進める上で取り組んだTypeScriptへの移行と開発時の工夫について私の方から紹介したいと思います
TypeScriptへの移行 上で述べたとおり現在 Wantedly のフロントエンドは 4世代あり、今回の主なターゲットである管理者画面はほとんど v2(React with JavaScript / Redux) で作られています。
v2 と v3 は同じリポジトリで管理され同じサーバーに存在するにも関わらず、異なる Webpack build が用いられていました。
上記記事で紹介しているように、技術基盤チーム(DXチーム)の取り組みで v2 と v3 の Webpack build が統一されたことによって副次的に v2 で TypeScript を利用できるようになりました。
静的型付けによるミスの削減や今後のメンテナンスのしやすさを考えると少しずつでもTypeScript 化を進めていきたいところです。とは言ってもすべてのページを TypeScript で書き直すには時間が足りません。そこで、以下の方針に沿って TypeScript への移行を進めていきました。
今回リノベ触らない既存のページに関しては一旦拡張子を js/jsx から ts/tsx に変更し、エラーが出る箇所には ts-ignore をつけ、リノベ後にリファクタリングする リノベで触るページ・新しく作るコンポーネントは ts/tsx で作成し、なるべく any を遣わないようにして適切な型をつける TypeScript の静的解析は非常に強力で実行前にバグに気付くことができるなど、TypeScript 移行前と後では開発効率に大きな差があると感じました。リノベが 1/3 ほど終わったタイミングでWebpack build が統一されたこともあり、前半に書いていたコードは TypeScript へ完全に移行できていない部分があります。今後は TypeScript への移行が完了していない部分の対応も進めていきたいと思います。
開発時の工夫 Wantedly には開発用の Kubernetes クラスタがあり、マイクロサービスやデータベースが本番環境と同じ構成で用意してあります。また、それらの環境には HTTP Header に特定の key:value を付けてアクセスすることによって A/Bテスト の振り分けの操作や指定したモーダルの表示を制御できる「feature flag」という機能があります。さらに、Wantedly へのすべてのリクエストに対して指定した HTTP Header を付与してくれる Chrome 拡張があり、feature flag を使う際はこれらを組み合わせて使っています。この節では feature flag を用いて行った開発時の工夫について述べたいと思います。
前提として Wantedly のフロントエンド v2 は Rails サーバーから返された HTML に script タグが埋め込まれており、ブラウザが CDN に置いてある JavaScript ファイルを取りに行きレンダリングします。普段どのように v2 の開発を行っているかというと、基本的にローカルで Rails サーバーと Webpack Dev Server の両方を立て、共有の DB に接続して開発・デバッグを行います。
先に述べた通り、今回のリノベではバックエンドの変更は翻訳ファイルの修正やAPIへのフィールド追加など軽微なものがほとんどです。それなのに Rails app をローカルで起動するのは面倒かつ PC の負担にもなります。バックエンドは共有の開発用クラスタを使い、ローカルで起動した Webpack Dev Server へアクセスすることができれば上記の問題を解決できそうです。
そこで feature flag を使い、「ある Header を指定した場合 HTML 中に埋め込む script を localhost に取りに行くように変更する」という実装を Rails app にしてあげます。これによって、共有の開発クラスタにアクセスしつつローカルで行ったフロントエンドの変更を確かめることができるようになります。
ただこれだけではサーバーサイドに手を入れる必要がある場合、共有の開発クラスタを専有しなければなりません。2つしかない開発環境を専有してしまうのは申し訳ないです。
実は Wantedly には既にこの問題を解決する手法が存在しています。詳細は以下の記事を参照していただきたいのですが、一言でいうと feature flag (を実現するため) の仕組みを利用して、特定の Header がついたリクエストを開発用クラスタ内の特定の pod に流す事ができます。
この仕組みを使うことにより、API の変更を行った Pod をクラスタ上に立ち上げ、フロントエンドはローカルの Webpack Dev Server で配信しているファイルを取りに行くということが実現できます。
Wantedly の開発を便利にするツールとそれを利用して共有の開発サーバーを使いつつフロントエンドの変更を確認する手法について紹介しました。一方で Webpack Dev Server の Hot Reloading 機能を利用することができないという問題もあり、こちらは今後の課題です。
まとめ 約半年間かけて、企業側管理画面をリノベーションしてきました。その過程で、負債になりそうであった技術スタックも少しずつアップデートを重ねました。
scss から styled-components に移行し、デザインシステム実装を使えるようにし、統一感のある UI/UX を実現できました。また TypeScript を使って開発生産性やバグの起きにくいプロダクト開発ができるような基盤作りもできました。
新規プロダクトである Engagement 領域や、昨年末にリニューアルをした Profile が新規のコードベース上で次々に新しい価値を生み出していく一方で、長年運用し価値を提供し続けているプロジェクト上ではまだまだレガシーと言えるコードは残っています。こうしたレガシーを改善したり、よりよりプロダクト開発のための基盤を作ったりすることに興味のある方はぜひ一度カジュアルにお話ししませんか?