♻️

Twig→Reactへの漸進的移行:モブワークで基幹システムをモダナイズしていく

2024/12/18に公開

この記事は「レバテック開発部 Advent Calendar 2024」の 18 日目の記事です!
昨日は tsuge さんが担当していました!

はじめに

こんにちは、レバテック開発部の岡田です
レバテックの営業さんが利用する社内営業支援システムの開発保守運用をやってます!

レバテックを支えていると言っても過言ではない大きなシステムです
システムのバックエンド側はPHPとLaravelで動いており、フロント側はTwigというテンプレートエンジンを使っています
最近、フロント部分をReact化していく活動をしているので、この記事ではなぜ進めているのかや具体的な対応内容を書いていこうと思います!

この記事でわかること

  • なぜリプレイスではなく少しずつモダナイズするのか
  • Twigを少しずつReactに置き換えていく方法
  • React初心者のチームでどう進めていくか

Twig→React移行の判断

当初は社内システムなので、フロント側をそんなにリッチにする必要はないのでは?という考えもありました
しかし、営業さん目線で考えてみると、使いづらいと部分はたくさんあるはずで、きっとユーザー体験の悪化から営業生産性が落ちている部分もあるのではないかと思っていました

システム自体の問題もありますが、具体的には、

  • 1項目更新したいだけでも、サーバーに送信しないとバリデーション結果がわからなかったり
  • 1つの業務をするのに複数画面を開かないといけなかったり
  • 1画面に大量のデータを表示しているのでパフォーマンスが悪かったり

そんな使いづらいシステムに時間を使わせてしまうのは勿体なく、人にしかできないことに集中してもらって営業生産性を上げた方が良いとなりました!

そんな背景から、Twig→React移行しつつユーザー体験を向上させていこうという判断がなされました

https://speakerdeck.com/leveragestech/mou-du-shi-ye-wozhi-erusisutemuni
より詳細に書かれている資料も公開されているので必見です!

まずは共存させる

当初はTwigで書かれている表示部分を完全にReactに移行していく流れでしたが、やりたいことに対して、ページ単位でReact化をしようとするとタスク自体が大きくなりすぎて、事業貢献を止めずに「ついで」にモダナイズするのはやりづらい
ちなみに、Next.jsを採用してSSRにするのはどうかと技術検証してみましたが、部分的に導入するには厳しそうだったので断念しました

Next.jsだと厳しくなる部分

  • ヘッダーとサイドバーが全ての画面で表示されているため、Next.jsで実装するには、ヘッダーとサイドバーもNext.jsで表示する必要が出てきて、二重管理しなければならなかった。
  • 中央のコンテンツ部分だけ表示するようにiFlameを使って埋め込みで試したときは上手くできそうでしたが、ブラウザバックしてしまうと中央部分にヘッダーとサイドバーが表示されてしまったりしました。
    (Google Meetで画面共有した時にたまに見るアレです笑)

採用技術の決定

というわけで、色々と検証してみたところ、ReactとViteを使って、Twigからコンポーネントを呼び出す形で進めることにしました!
Viteにしたのは、Laravelでも9系から標準採用になっているので、既存の技術スタックとの親和性も高かったからです
とはいえ、バックエンド側のLaravelはまだWebpackを使用してて、Viteに完全移行はできていません笑

モダナイズに向けたチームの取り組み

どう進めるか

営業支援システムは規模も大きく複数事業に跨って利用されているので、開発チームは大雑把な領域で分けて、4つのチームで開発保守運用をしています

いざ、React化していくぞー!となっても、全員にReact経験があるわけではなく、経験がある人だけで実装していくとそれだけ進みは遅くなってしまいます
そこで、イネイブリングチームにサポートしてもらいながら、未経験メンバーのスキル習得も兼ねてモブワーク形式で実践していくことにしました!

React DOJO で修行を積む

まずは、イネイブリングチームから出された簡単なお題をクリアするために、Reactチュートリアルをざっと読んで試しました
例えば、

  • divタグをコンポーネント化して表示する
  • コンポーネント化したものに、propsを渡してdivタグ内で表示する
  • リストを表示してstate化してみる
  • fetch apiとasync awaitを使ってサンプル用のAPIから取得する

など、裏側の処理とかの基礎から解説を受けました

モブワークの進め方

通常だと、ドライバーとナビゲーターを順番に回していくと思いますが、今回はスキルアップを兼ねてナビゲーターはイネイブリングチームが担当し、ドライバーを順番に回していくことにしました
基本的にナビゲーターにフォローしてもらいながらドライバーが実装していき、都度Reactの仕様説明をしてもらったり、実装を進めるときのコツやcopilotの活用、チームで仕事するときに意識していることなどのソフトスキル面の話をしてもらいながら進めました

本来のモブワークの進め方とは違うかもしれませんが、イネイブリングチームの方を師範と見立ててこの活動をDOJOと呼ぶことにしています笑

DOJOの終わりにやったことの共有

成果物

DOJOでの成果物としては、1つのページをReact化することを目標にしました!
手始めにReact化するページは下記の理由から決定しました

  • 複雑なロジックがない
  • 進めている最中に変更が入らない
  • 他のページでも流用できるコンポーネントが作れる

まずは「ついでにモダナイズする」という方針ではありますが、Reactを実装できるようになる必要があったので、致し方ないコストかなと思っています

具体例

ここからは、どうやってTwigにReactを埋め込むのか具体例で説明できればと思います

とあるTwigファイル
{% extends 'layouts/default' %}

{% block content %}
    {% embed 'components/filters/template' %}
        {% block header %}
            <h1>画面一覧</h1>
        {% endblock %}
        {% block filters %}
            {% include 'components/samplePage/search/filter' %}
        {% endblock %}
        {% block results %}
            {% include 'components/samplePage/search/result' %}
        {% endblock %}
    {% endembed %}
{% endblock %}

とある機能の一覧画面を表示するTwigファイルです
検索条件や検索結果を表示するTwigファイルも別で用意されていて、それぞれincludeすることによって表示されるようになっています

これはこれですごくシンプルに纏まってはいそうですが、React化した後は下記のようになりました!
Twigファイルでは、componentファイルを読み込む用の記述をして、tsxファイルに今までと同じ表示になるように実装していきます

React化したTwigファイル
{% extends 'layouts/default' %}
{% block content %}
    <div id="root"></div>
    <input
        type="hidden"
        id="reactc:root:/src/pages/sample.tsx"
        name="__react_params"
        value='{}'
    />
{% endblock %}
src/pages/sample.tsx
export default function Sample(){
  return <div>Reactだよん</div>
}

(内容は簡略化して書いてあります🙏)

すごく、ものすごく!シンプルに実装できるようになりました!

上記の記述だけで表示されるようになったのは、イネイブリングチームの方が基盤を実装してくれたおかげなんです
本当にありがたいことです!!

TwigとReactを共存させるために実装してもらった基盤の一部分です

main.tsx
function view({
  paramKey,   // name="__react_params"の部分が渡される
  moduleName, // /src/pages/sample.tsxが渡される
}: {
  paramKey: string
  moduleName: string
}) {
  const ROUTES = import.meta.glob('/src/pages/**/[A-Z]*.tsx', {
    import: 'default',
    eager: true,
  })
  const Component = ROUTES[moduleName] as React.ComponentType

  // コンポーネントに渡すパラメータを取得する
  const valueStr = (document.getElementById(paramKey) as HTMLInputElement)?.value || ''
  const data = valueStr === '' ? {} : JSON.parse(valueStr)

  const RenderComponent = () => {
    return (
      <>
        <React.StrictMode>
          <Component {...data} />
        </React.StrictMode>
      </>
    )
  }
  ReactDOM.createRoot(targetElement).render(<RenderComponent />)
  return
}

このようにして、部分的にTwig→React化が実現できるようになりました!
Laravelの標準で採用されているBladeテンプレートでも同様の置き換えが実現できると思うので、システムが巨大すぎて、モダンな技術に変えられないよと困っている方の助けになればと思います!

定量評価も取れるようにしました

余談ですが、CIでTwigをReactに置き換えられた割合をSlackで通知されるようにもしています
定量的に観測できると個人的にはモチベーションが上がります🔥


現在1%、がんばるぞ!w

さいごに

すでに実務のなかでDOJOの経験を活かして一部React化を進めたりしています
ユーザーの営業さんにも、更新ボタンを押さないとバリデーションメッセージが返ってこない、テンプレートエンジン由来の体験がReact化によって改善されて、感謝の言葉をもらうことができたりしました!!🎉

多くある機能のうちのほんの一部しか対応できていないので、システムをモダナイズしていく戦いはまだまだ続きそうです!

明日は みやてく さんがモブワークについての記事を投稿します~
レバテック開発部 Advent Calendar 2024」をぜひご購読ください!

レバテック開発部

Discussion