GameWith Developer Blog

GameWith のエンジニア、デザイナーが技術について日々発信していきます。

GDS のビルド方式をパッケージビルド方式に変更しました #GameWith #TechWith

はじめに

こんにちは!スケールアーキテクトチームの @53able, @inosy22, @nog です!

今回は GDS のビルドの構成を変更したので、紹介していきたいと思います!

GDS についてはこちらのブログで紹介していますので、御覧ください。

tech.gamewith.co.jp

課題

GDS での開発も1年半が経ち、いくつか課題が見えてきました。

ビルド成果物が一つにまとまっているため、下記のような課題があります。

  • 開発/リリースの度に他の機能への影響を及ぼす可能性がある
  • 不要な箇所で不要な機能まで読み込まれていることが多く、無駄な通信が発生している
  • また、リリースの度にバージョンが変わるため変更をしていない箇所の JS のキャッシュが消え再読み込みが走り、無駄な通信が発生している
  • 開発時にバージョンがよくコンフリクトする
  • ビルドにかかる時間が長くなってきた

これらの課題を解決するためにビルド成果物を1つにまとめず、パッケージ単位(幾つかのコンポーネントをバンドルした単位)で分割してビルドする方式に変更をしました。

今までのビルド方式

ビルドターゲットを全てのコンポーネントにしており、ビルド成果物を1つにまとめていました(ビルドは下記コマンドを叩くイメージです)

$ yarn build

生成されるパスは以下のようになっており、ひとつのバージョンに対してビルドする度に全てのコンポーネントが更新されていました。

gds/${version}/gds.min.js
gds/${version}/gds.0.min.js
gds/${version}/gds.1.min.js
gds/${version}/gds.2.min.js
gds/${version}/gds.3.min.js
...

パッケージビルド方式

変更後のビルド方式はパッケージ単位でビルドし、更新するコンポーネントを減らしました。

$ PKG=Sample1 yarn pkg
gds-packages/sample1/${version}/gds-sample1.min.js
gds-packages/sample1/${version}/gds-sample1.0.min.js
gds-packages/sample1/${version}/gds-sample1.1.min.js
gds-packages/sample1/${version}/gds-sample1.2.min.js
gds-packages/sample1/${version}/gds-sample1.3.min.js
...

$ PKG=Sample2 yarn pkg
gds-packages/sample2/${version}/gds-sample2.min.js
gds-packages/sample2/${version}/gds-sample2.0.min.js
gds-packages/sample2/${version}/gds-sample2.1.min.js
gds-packages/sample2/${version}/gds-sample2.2.min.js
gds-packages/sample2/${version}/gds-sample2.3.min.js
...

パッケージビルド方式では、パッケージ単位でバージョンを管理しているため、上記のようにパッケージ名がパスに含まれています。

CI/CD 環境

今までのバージョンの指定は package.jsonversion を利用していました。

パッケージビルド方式ではパッケージ名ごとにバージョンが違うため、各パッケージのディレクトリに .version ファイルを設置し利用しています。

今までの CI/CD では差分などはチェックせず毎回ビルドを行っていました。

パッケージビルド方式では開発中の CI/CD では git diff を行い master ブランチの差分を元にデプロイの対象を選定します。また、 .version ファイルが設置されていない場合はリリース対象になりません。

本番環境の CI/CD では S3 にデプロイされているバージョンと比較し、大きい場合のみデプロイされます。

苦労した点

複数のパッケージを同じページで読み込むと、一部のパッケージの WebComponents が使えないケースがありました。

この原因を調査して解決するには、 vue-cli によってビルドされた成果物が、WebComponents を使えるようにするための、内部の構造を理解する必要があったので、紹介させていただきます。

新しいパスを見て気づいた人もいるかもしれませんが、ファイル名にもパッケージ名を入れています。

すでに ${package-name}/${version} のようにパスにパッケージ名を入れているので、ファイル名はどのパッケージ名でも gds.min.js にしてもよいはずです(わざわざパッケージ名を入れる必要はありません)。

しかし、ファイル名にパッケージ名を入れているのは理由があります。

vue-cli からビルドされた成果物の内部コードでは、ファイル名をキーにして window にオブジェクトが新しく作られます。

そのため、ファイル名を gds.min.js で共通にしてしまうと、window["gds_jsonp"] に対して上書きし合うため、バグの温床になってしまいます。

ファイル名にパッケージ名を入れることで gds-xxx.min.js になりますが window["gds-xxx_json"] になるため、上書きを回避できます。

$ vue-cli-service build --target wc-async --inline-vue --name gds ...
(window["gds_jsonp"] = window["gds_jsonp"] || []).push([[0], ... }]);

$ vue-cli-service build --target wc-async --inline-vue --name gds-sample1 ...
(window["gds-sample1_jsonp"] = window["gds-sample1_jsonp"] || []).push([[0], ... }]);

http:// https://cli.vuejs.org/guide/build-targets.html#async-web-component

最後に

ブログの冒頭であげていたこれらの課題は、パッケージビルド方式にすることで解決されました!

開発/リリースの度に他の機能への影響を及ぼす可能性がある

👉 パッケージごとにビルドされるため、他のパッケージに影響を及ぼすことはありません。

不要な箇所で不要な機能まで読み込まれていることが多く、無駄な通信が発生している

👉 必要なページで、必要なパッケージを読み込むようにすることで、通信量を削減できるようになりました。

また、リリースの度にバージョンが変わるため修正をしていない箇所の JS のキャッシュが消え再読み込みが走り、無駄な通信が発生している

👉 リリースが行われても対象外のパッケージのバージョンは変わらないため、引き続きがキャシュが有効になり無駄な通信は発生しなくなりました。

開発時にバージョンがよくコンフリクトする

👉 パッケージ単位で .version が定義されるため、コンフリクトがほぼ発生しなくなり分担して作業が行いやすくなりました。

ビルドにかかる時間が長くなってきた

👉 概ね30秒程度短縮されて、1分以内でビルドが完了するようになりました。

パッケージビルド方式での課題としては、以下があります。

  • 引き続きバージョンは手動で書き換える必要がある
  • 実は全てのコンポーネントをパッケージビルド方式に変更しておらず、新旧のビルド方式が混ざっている
  • 全パッケージで利用しているファイルを修正した際に、全パッケージのバージョンを変更する必要がある
    • パッケージ毎にビルドできるメリットの裏側的な
  • パッケージを横断する修正を行った場合に、複数パッケージをリリースする必要がある

今回の改修でコンポーネント実装がよりスケールできるようになりました!

今後もより良いコンポーネントを作り、GameWith を改善していきます!

Twitter

Twitter にてテックブログの投稿をツイートしていますので、よろしければフォローをお願いします!

twitter.com

Wanted!

一緒に働く仲間(特にサーバサイドエンジニア)を絶賛募集中です!

以下 Wantedly のページからぜひカジュアル面談へお申し込みください!

www.wantedly.com