SMARTCAMP Engineer Blog

スマートキャンプ株式会社(SMARTCAMP Co., Ltd.)のエンジニアブログです。業務で取り入れた新しい技術や試行錯誤を知見として共有していきます。

若手エンジニアの俺がフロントエンドのビルドを早くしてReactも導入しちゃった話

またオレ何かやっちゃいました?

こんにちは!!!スマートキャンプでエンジニアをしている吉永です!

弊社の主力サービスであるBOXILはリリースから時間が経っていることもあり、バックエンド・フロントエンドともに様々な技術的負債となる部分を抱えています。

また、その負債の中には普段の業務時間では手をつけにくいものもあるため、定期的に薪入れと呼ばれる開発改善日を設けています。

先月行われた薪入れはいつもよりも長い1週間の期間が設定されており、今回の記事ではその中で私が行ったフロントエンド改善の内容や、そこに至った経緯について説明します。

やったこと

薪入れではそれぞれで改善する目標を持つのですが、私は

  • CIによるフロントエンドのビルド時間を短縮したい
  • Reactを試験導入してみて、ちょっとした動作をさせてみる

と言う2つの目標を設定しました。

結論としては、以下の2点が達成されました。

ビルドフロントの短縮

こちらは、ビルド時間をいかに早くできるかを有志のメンバーで競い合う形で実施され、結果として

ローカルビルド時間: 5分8秒 -> 52秒
CIによるビルド時間: 8分 -> 4分

に改善されました。

Reactの試験導入

こちらは本番環境にすぐ反映させるというものではなく、まずは既存の環境で使えるかテストしてみるという意図で、Webpack周りの理解も含めて既存アーキテクチャで「Reactが動けば成功」という目標で導入してみました。

また、TSXが使えるかなどのテストも合わせて実施したため、何をやったの詳細は後述します。

BOXILのフロントエンドについて

先ほど少し説明した通り、BOXILはリリースから6年以上が経過している弊社創業間もなくから存在するプロダクトです。

そのため、時間が経つにつれて技術的な流行りもどんどん変容していき、現在はフロントエンドだけでもかなりの負債や、時代を感じさせられる技術選定が行われている箇所があります。

しかし、その負債を解消しきれずに放置してしまった事による可読性の低下や、新規メンバーの開発参入時にかかる理解コスト、採用面接時に「まだCoffeeScriptで消耗してるの?」と捉えられてしまう悲しさなど様々なデメリットが発生しています。

そのため直近では私の主導でCSSの検索性改善やBEM化などが行われましたが、今回また新たな試みとして、これまでも議論されていたものの解決できていなかったビルド時間の短縮やReactの導入テストに踏み切った次第です。

現在のフロントエンドを改善するに至った経緯とその背景

現状、BOXILのフロントエンドの採用技術は以下のようになっています。

構成は一見するとシンプルですが、

  • CoffeeScriptやjQueryを使って書かれている画面
  • AppのCoffeeScriptの中でnew Vue...とすることによりVueコンポーネントとする使い方
  • Vue + TypeScriptを採用したときに一時期使っていたClassベースのComponent

など幅広いレガシーな負債が存在しています。

また、BOXILフロントエンドの今後の改善案として、最近ではデザイナーとの連携を強化するためのデザインシステムの導入案やテストを強化する案などもチーム内で挙がっています。

そのため、まずチーム内でどこを負債と感じ、優先して対応するべきものはどれなのかを決めたいと思いました。

また、対応する理由なども全体で共通認識を持っておくべきだと思い、フロントエンドを話す会という名目で数日に分けてミーティングを設定しました。

フロントエンド負債の認識のすり合わせ

チーム内での負債の認識や、優先したい対応の確認などは今後の改善に大きく関わってくると思ったため最優先で対応することにしました。

また、負債を解消するにあたっては今後の展望を見据えたうえで技術選定を行っていきたいと考えていたため、現状の負債と今後の理想といった形の2つのミーティングをBOXIL開発チーム全体で取ることにより今後のロードマップを敷くことまでを目標にしていました。

負債を話し合う会の開催

まず行ったのは負債を話し合う会の開催です。

共同編集できるドキュメントに参加者が思う負債の内容を列挙してもらい、これらに関してどう問題なのかなどを話しました。

ほんの一例ですが、書かれていた内容を貼り付けてみました。

こう見ると、同じVueでも書き方が統一されていない(フォーマット問題)やCoffeeScriptに関する問題などが挙げられていることがわかります。

また、この一連の流れでCIで行っているbuild_front(フロントエンドをビルドするジョブ)があまりにも遅いという問題が再び課題として上がっていました。

理想を話し合う会の開催

次に理想となる環境を話し合ってみました。

前提として、理想は理想であるため、すぐすぐ解決できなくとも、「本来はこうあればいいよね」といったものも込みで話し合うことにしました。

負債の時と同じように各々が感じる理想を列挙してもらい、それに対して参加者全員で工数・解決したいことを踏まえた優先度(やりたいお気持ち度)を1~5ポイントで投票してもらい、最終的に解決したいものを決定しました。

そして、先述したReactに関しての議論も行われ、上記のポイントの高いものやCoffeeScriptなどレガシーな技術が使用されている箇所からスモールステップでReactに作り変えていこうという話になりました。

React導入に向けた動き出し

Reactをえいやで導入した結果、後々負債になってしまいましたといった結末は避けたいと感じていました。

その為、これまでVue + TypeScriptの構成を選んでいた弊社開発チームがどれだけReactに学習コストを割けるのか、また現状で構成をシフトした場合に教えられるメンバーがいるのかなどの調査をすることにしました。

リスクとして、React化が途中で頓挫した場合

  • Vueページ
  • Reactページ
  • CoffeeScriptページ

と言った三国志の様な状態になるのがまず懸念としてありました。

また、Webpackが整備されていないことや、この辺りの実装当時に関わっていたメンバーが少ないこともあったため、不安視する声も上がっていました。

しかし、デザインシステム導入の話がデザイナーサイドや一部のエンジニアメンバーで議論されているということもあり、折角であれば最小単位のパーツやあまり要素の多くないページをReactで作り始めることができれば、当初の狙い通り小さい走り出しができるのではないかと考えました。 また、エンジニア採用面でも今後Reactの採用は強みになりそうだという意見や、新しい技術への挑戦という意味でも採用するメリットは高いと感じました。

また、開発チーム全体でReactの導入経験が全くの0というわけではなく、近ごろ走り始めたプロダクトではReactやGoといったこれまで興味はあるものの使ってこなかった技術スタックを試しています。

そこで身につけた知見や技術を横展開できる場としても、メインプロダクトへのReact導入は良い方向に進む可能性が高いと感じました。

React導入を決定してから行ったこと

Reactに知見のあるメンバーと開発リーダーを呼んだミーティングを行い、導入するとしてどうやって進めて行こう、何から始めようといった内容を話し合い決定しました。

頓挫したときに切り戻しやすい部分から改修していきたいという意見があり、やり始めるとしたら、BOXILの中でもあまりパーツ数が多い方ではない管理画面のとあるページがいいのではないかとなりました。

また、導入までのフロー案として、

  1. 既存機能や新規機能をReactにする
  2. 管理画面自体をReactプロジェクトとして引き剥がす

と言った2択の意見も出ていました。

しかし、2の方はかなり改修が大掛かりになってしまう上に、現状かけられるコストとしてもそこまでの工数は割けないという判断となり、1を採用することにしました。

そして1を導入するにあたり、Reactの最小構成を作るときに何を予め準備すべきかといった会話から、最小限導入しておくものを決定しました。

また、React導入後に知見を共有したり、開発メンバーが戸惑うことなく開発できるように、周知や勉強会の実施などの意見出しなども行っていました。

そして、一旦の最終目標として

  • Vue
    • Classベースのもの: extendsの書き方に変えていく
    • extendsのもの: そのまま
  • CoffeeScriptまたはCoffeeScript + Vue
    • 順次Reactに置き換えていく

として最終的にVueとReactのファイルだけに統一され、その後Reactに順次Vueのものを置き換えていく...という流れが良いだろうと判断しました。

最終的に決定したもの

この結果を踏まえて、薪入れで対応するタスクを

  • ビルド時間の短縮
  • Reactの導入テスト

にしました。

ビルド時間の短縮は、煩雑になってしまっているWebpack周りの処理を読み、適切な処理に書き直してあげる必要がありそうだったため、実装理解という点でもReact導入の障壁になり得ると考えられていたリスクの一つを減らせるのでは無いかと思いました。

ビルド時間の短縮でやったこと

ビルド時間改善は、参加するメンバーが各々の考えた方法で高速化を試してみるというスタンスで開催しました。

今回は私が行ったthread-loaderを使った短縮を説明します。

thread-loader

Webpackを使用して開発を行う場合、rulesの中にファイルの拡張子ごとにloaderを設定し処理を行わせると思います。 thread-loaderでは、そのloaderの処理を並列で実行することによってビルド時間の短縮を可能にします。

他に検討した方法

hard-source-webpack-pluginesbuild-loaderの導入も候補として検討しました。

が、esbuild-loaderにおいてはVueでも同じように使えるのか、それともViteを使っていくべきなのかという問題があった上、現状BOXILではIEユーザーがいまだに一定数いるためIEに対応していない方法では解決できないという結論になりました。

thread-loaderの導入方法

ここからはthread-loaderの導入方法について説明します。

この時参考にさせて頂いた資料はこちらです。 * esbuild導入時にも様々なアドバイスを頂きました。ありがとうございました。

qiita.com

npmモジュールのインストール

以下のコマンドでインストールが可能です。

npm i -D thread-loader

Webpackの各種rulesを編集する

{
  test: /\.ts$/,
  exclude: '/node_modules/',
  use: [
    // 各loaderの一番最初に記載する
    {
      loader: 'thread-loader',
      options: {
        workers: 2,
        workerParallelJobs: 80,
        workerNodeArgs: ['--max-old-space-size=512'],
        name: 'ts-loader-pool',
      }
    },
    {
      loader: 'babel-loader?cacheDirectory',
      options: {
        presets: [
          '@babel/preset-env',
        ],
      }
    },
    {
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/],
        transpileOnly: hmr,
        happyPackMode: true
      }
    }
  ]
},

これでthread-loaderの導入は完了です。

他にもオプションとして、thread-loaderをwarmupしておくことで、実行時の遅延を防ぐこともできますが今回は時間の関係もあり、一旦直接組み込む形で解決しました。

導入した結果、

ローカルビルド時間: 5分8秒 -> 52秒
CIによるビルド時間: 8分 -> 4分

となり、かなりのビルド時間短縮になりました。

Reactの導入方法

Reactやreact-domなどをインストールした後、各種ファイルを以下のように編集します。

・webpack

...
// tsxも読み込めるように
rules: [
  {
    test:/\.(ts|tsx)$/,
    exclude: '/node_modules/',
    loader: 'ts-loader',
    options: {
      appendTsSuffixTo: [/\.vue$/],
      transpileOnly: hmr,
    }
  },
]
...
// tsxを追加
resolve: {
  extensions: ['.vue', '.js', '.ts', '.tsx'],
  alias: {
    '@': resolve('./src/script')
  }
},

・tsconfig.json

{
  ...
  "compilerOptions": {
    // これを追加
    "jsx": "react",
  },
  ...
  "include": [
    // これを追加
    "./frontend/src/script/**/*.tsx"
  ]
}

そして、Vueファイルなどを読み込んでいるエントリーポイントをまとめている場所に、.tsxの形式で適当なコンポーネントを作ってslim内で読み込んでみます。すると...

ちょっとわかりにくいですが、BOXILのローカル環境に、よくあるクリックすると数字が増えるコンポーネントを導入してみた例です。

このように.tsxで書かれたReactのコードがしっかりと動作していることがわかりました。

まとめ

今回目標にしていた、ビルド時間の短縮とReactの導入はどちらも達成することができました。

また、Webpack周りの処理を深く理解することが出来たため今後の開発において懸念する点を少し減らすことが出来たと考えています。

課題として

  • hard-source-webpack-pluginとthread-loaderの連携が上手くいかなかった
  • Webpackの理解に時間を取られすぎてしまい、jestなどの導入までには至らなかった

など、今後も薪入れの時間などを使って改善しなくてはいけないと感じる点が多かったです。

さらに、Reactを導入することだけが本来の目標ではなく、最終的な目標はCoffeeScriptなどのレガシーな環境をモダンにするという内容のため、ここから更に知見を深め、チーム全体での技術レベルの向上に努めなければいけないと感じています。

レガシー環境の改善は、これまでプロダクトが歩んできた分だけ溜まってしまう物ではあると認識しているため、これからも継続して負債の返済にあたり、明日結果を出すことに期待するのではなく、一年後、二年後を見据えた開発ができる環境作りに励みたいと思いました。

この記事が同じ課題を抱えている方々の助けになれば幸いです。