GameWith Developer Blog

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

New GA でレポーティングを行うTips #GameWith #TechWith

はじめに

こんにちは。GameWith のエンジニアの m です!

このブログはアドベントカレンダーの20日目のブログになります!

qiita.com

今回は New GA について記事を書きます!

New GA とは

2020年10月14日に正式リリースされた次世代版の Google アナリティクスです。

Google Analytics 4 property(GA4) としてリリースされましたが、正式名称は New Google Analytics(New GA) になるようです。

従来の ユニバーサルアナリティクス(UA)との違いは以下の記事でご紹介しています。

tech.gamewith.co.jp

New GA でデータ利用をする上での課題

アナリティクスのダッシュボードが使いづらいところが最大の課題だと思っています。

UAの 行動分析 であればディメンションを2つまで指定して、ページビューやイベントのデータをある程度簡単に視覚化することができ、より詳細なカスタムレポートを作成・共有することができました。

New GAでは エンゲージメント分析 で似たようなことができますが、パラメータ(ディメンションに相当)が1つしか指定できず、表示件数も少ないです。

データ探索 も使い勝手が悪いため柔軟に見やすいレポートを作るために、データポータルやBIツールを利用するケースが増えると思います。

今回はNew GAからBig Queryにデータをエクスポートし、データポータルで視覚化する際のTipsをご紹介します。

イベント設計時のTips

タグマネージャーを利用してパラメータを管理

New GAではタグマネージャーのタグタイプが 設定イベント に分かれています。

設定タグ はアナリティクスのプロパティIDを指定し、全ページに埋め込んでページビュー イベントの計測に利用します。

イベントタグ はクリックイベントなどを個別に定義するものですが、 設定タグ を紐づけることで 設定タグ のフィールドをイベントパラメータに引き継ぐことができます。

ユーザーIDなどの全てのイベントで共通して取得したいパラメータは 設定タグ で一元管理できるので管理がとても楽になります。

f:id:takuya_minami373:20211220093347p:plain
タグマネージャータグタイプ

文字数の長いパラメータの収集

UAのカスタムディメンションには文字数の制限がありませんでしたが、New GAのイベントパラメータは以下の制約があります。

項目 上限
パラメータ数 25
パラメータ名の長さ 40文字
パラメータ値の長さ 100文字

弊社ではユーザーエージェントがこの制約に引っかかりました。

元々はそのまま送信して集計側で分類していましたが、イベント送信前に細分化する方法に切り替えました。

イベントパラメータの値は STRING, INTEGER, FLOAT のいずれかに自動で型判別されます。

型の指定はできないため誤解が生じないよう is_xxx などBooleanで理解できるパラメータ名にして 1 or 0 で送信しています。

タグマネージャー内で 変数 > ユーザー定義変数 > JavaScript変数 を利用すれば分類を完結することも可能です。

イベント集計時のTips

可能な限り集約してテーブル化する

BigQueryのデータを利用する場合、データの参照量に応じて従量課金が発生します。

そのため、エクスポートしたデータを直接参照するのではなく、集約してテーブル化することでコストを抑えています。

デバイス情報関連では以下のカラムをグルーピングに使うことが多いです。

項目 説明
device.operating_system デバイスのOS iOS, Android, Windows
device.category デバイスカテゴリ mobile, desktop, tablet
device.web_info.browser ブラウザ Safari, Chrome, Safari (in-app)

テーブル化には洗い替え方式のスケジュールクエリを使う

スケジュールクエリは、ログテーブルの集約テーブル化を自動で行ってくれる機能です。 冪等性の担保するためのポイントが以下になります。

Query string

日時指定に@run_date または @run_time を用います。

これらはスケジュールクエリ実行時のDATE型 or TIMESTAMP型に変換されるパラメータです。

CURRENT_DATE() 等を使うとバックフィルの実行時にズレが発生しますがそれを防ぐことができます。

cloud.google.com

宛先テーブルの書き込み設定(Write preference)

WRITE_TRUNCATE に設定しテーブルのデータを上書きします。

上書きではなく追加( WRITE_APPEND )を設定してしまうとデータ重複する可能性があるため、テーブルを日付パーティションで分割して洗い替える運用にしています。

テーブル名(Destination table)

日付のパーティションを以下のフォーマットで設定します。

前日の日付でパーティションをきる例: TABLE_NAME${run_time-24h|"%Y%m%d"}

テーブルの日付フィールドをパーティションに指定することも可能です。 その場合はテーブル名の $ 以降の記述は不要で、以下の項目にフィールド名を入力します。

宛先テーブルのパーティショニング フィールド (Partitioning field)

単純な集約関数では要件を満たせない場合

SUM() AVG() などで問題がある場合は ARRAY_AGG() が便利です。

配列でデータを持つ

BigQueryは REPEATED モードのフィールドを使えば、複数のデータを1レコードに持つことが可能です。

ARRAY_AGG(hoge)

一意なデータにする

一度 ARRAY_AGG で配列にした後、 OFFSET(0) で一つだけ抽出することで実現できます。

DISTINCT や NULL除外、並び替えも ARRAY_AGG() 内でできるので便利です。

ARRAY_AGG(DISTINCT hoge IGNORE NULLS ORDER BY fuga DESC LIMIT 1)[OFFSET(0)]

終わりに

有償版の360も発表されNew GAがデフォルトのプロパティとなりつつあります。

UAと同じ使用感ですぐにNew GAを利用するのは難しいので、早めに並行稼働を開始して移行準備をして行きましょう!

Twitter

GameWithのDeveloper向けTwitterアカウントも開設しました。

ブログの更新情報などを発信するので良かったらフォロー宜しくお願いします!

twitter.com

動画の操作のログを送信する #GameWith #TechWith

はじめに

こんにちは。GameWith のエンジニアの tiwu です!

この記事は GameWith アドベントカレンダーの 8 日目の記事になります!

qiita.com

今回は video タグで再生される動画の操作ログの取得方法について書いていこうと思います。

動画のログについて

button タグなどのクリックのログであれば対象のタグに対して addEventListenerclick イベントを付与しログの送信を行うことが多いです。

しかし video タグ内にある再生、音量、シークなどの各機能を制御する UI に対して addEventListenerclick イベントの付与をすることはできません。

f:id:tiwu:20211122121919p:plain

そのため動画に関するログを送信する場合は、video タグが発生させるイベントを利用しログの送信を行います。

developer.mozilla.org

紹介するイベントは最大化(fullscreenchange)を除き、 video タグの継承元である HTMLMediaElement のイベントになります。

developer.mozilla.org

再生

動画が再生した際に play イベントが発火します。

developer.mozilla.org

発火するタイミングは主に

  • autoplay プロパティによる自動再生
  • 停止後、再生が行われた時
  • 終了後、再生が行われた時

があります。

サンプルコード

const videoElement = document.querySelector("video");
let isEnd = false;
let isPause = false;
videoElement.addEventListener("play", () => {
  if (isEnd) {
    console.log("動画終了後に再生");
  } else if (isPause) {
    console.log("停止後に再生");
  } else {
    console.log("自動再生");
  }

  isPause = false;
  isEnd = false;
});

videoElement.addEventListener("pause", () => {
  isPause = true;
});

videoElement.addEventListener("ended", () => {
  isEnd = true;
});

autoplay プロパティによる自動再生

autoplay プロパティを利用すると自動で動画が再生されます。

developer.mozilla.org

autoplay による自動再生か判断するのは難しいですが後述する他のイベントを判断することで、自動再生か判断します。

停止後、再生が行われた時

pause イベントが必ず発生後に、play イベントが発火するため変数を利用し判断しています。

終了後、再生が行われた時

ended イベントが必ず発生するため、停止後と同様に変数を利用し判断しています。

終了後の再生は currentTime プロパティが必ず 0 になっており、自動再生ではネット環境の影響か 0 になることは筆者の環境ではありませんでした。

そのため currentTime プロパティで判断することもできそうです。

developer.mozilla.org

※自動再生による再生で 0 以外になるかは確証がないため参考程度に読んでもらえればと🙏

videoElement.addEventListener("play", () => {
  if (videoElement.currentTime === 0) {
    console.log("動画終了後に再生");
  }
});

停止

動画が停止した際に pause イベントが発火します。

developer.mozilla.org

発火するタイミングは主に

  • 停止した時
    • 停止 UI のクリック
    • pause メソッドの実行で停止した時
  • 終了した時

があります。

サンプルコード

const videoElement = document.querySelector("video");
videoElement.addEventListener("pause", () => {
  if (videoElement.ended) {
    console.log("動画終了したため停止");
  } else {
    console.log("停止");
  }
});

この2つのタイミングは ended プロパティにより判断します。

developer.mozilla.org

停止した時

停止時の pause イベント内では ended プロパティは false になっているのを利用し判断します。

終了した時

動画が終了した際は pauseended の順にイベントが発火します。

終了時の pause イベント内では ended プロパティは true になっているのを利用し判断します。

終了

動画が終了した際に ended イベントが発火します。

developer.mozilla.org

サンプルコード

const videoElement = document.querySelector("video");
videoElement.addEventListener("ended", () => {
  console.log("動画終了");
});

終了以外のタイミングで発火しないため特に制御なしで実装しています。

音量

動画の音量が変化した際に volumechange イベントが発火します。

developer.mozilla.org

サンプルコード

const videoElement = document.querySelector("video");
videoElement.addEventListener("volumechange", () => {
  if (videoElement.muted === true) {
    console.log("音声が on -> off");
  }
});

音量が ON の状態から OFF に変更した際は mutedtrue になっているのを利用し判断します。

最大化

動画を最大化した際に video タグではイベントは発火せず、Documentfullscreenchange イベントが発火します。

developer.mozilla.org

サンプルコード

const fireFullScreenChange = () => {
  const fullScreen =
      document.fullscreenElement ||
      document.mozFullScreenElement ||
      document.webkitFullscreenElement ||
      document.msFullscreenElement;
  if (fullScreen && fullScreen.classList.contains("video")) {
    console.log("動画が最大化");
  }
};

document.addEventListener(
  "webkitfullscreenchange",
  fireFullScreenChange
);
document.addEventListener(
  "mozfullscreenchange",
  fireFullScreenChange
);
document.addEventListener(
  "MSFullscreenChange",
  fireFullScreenChange
);
document.addEventListener(
  "fullscreenchange",
  fireFullScreenChange
);

このイベントは Document で発生するため、最大化した要素が指定の要素か判断するために document.fullscreenElement を利用します。

developer.mozilla.org

サンプルコードでは最大化した要素に video クラスがあるか判定しています。

余談

このブログを書いている際に調べていて気づいたのですが Element でも fullscreenchange は発生するようで、requestFullscreen を利用すれば div タグなども全画面にすることができるそうです 👀

developer.mozilla.org

終わりに

MDN の公式を読んでいるといろいろな便利なイベントやメソッドがあり、直接クリックでログを仕込めなくても工夫しログを送信することができました✌️

この記事が何かの参考になればと思います!

Twitter

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

twitter.com

Wanted!

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

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

www.wantedly.com

2021年 GameWith の技術広報の1年間の振り返りとこれから #GameWith #TechWith

こんにちは。GameWith のエンジニアの tiwu です。

今年も始まりましたアドベントカレンダー!

1発目のブログは去年に引き続き、今年1年間の技術広報活動を振り返っていこうと思います!

qiita.com

Blog

今年(2021年12月1日時点)の投稿数は 13 本でした!

去年が 24 本だったので、約半分の投稿数になります。

PV は2万3千ほどになります!去年は2万4千ほどだったので、投稿数は半分ですが、PVは去年とほぼ同じでした🎉

f:id:tiwu:20211129120228p:plain
2021年のPVの推移

f:id:tiwu:20211125141147p:plain
2020年のPVの推移

f:id:tiwu:20211126190403p:plain
2019年のPVの推移

ちなみに今年1番PVの多かった記事はこちらの記事でした!

tech.gamewith.co.jp

月1のリモートペア・モブブログ

弊社はフルリモートワークを実施しているので、今まで行っていたリアルでのペアブログ・モブブログは実施できなくなりました。

ペアブログ・モブブログ自体は以前から月に1回開催をしており、フルリモートワークになって今でも頻度を変えず月1で実施しています。

フルリモートワーク環境では Google Meet でビデオ会議を行い、VSCode Live Share を利用して執筆作業を行っています!

詳しくはこちらの記事を御覧ください。

tech.gamewith.co.jp

ポッドキャスト

新たな取り組みとして GameWith メンバーが企画し、ポッドキャストでの発信も行いました!

tech.gamewith.co.jp

GameWith メンバー3人の談義が収録されているので是非聞いてみてください!

GitHub Repository

また、新たな取り組みとして @serima がリードして、採用情報をまとめた GitHub Repository を公開しました。

tech.gamewith.co.jp

先週採用技術の更新をしたので Repository も是非チェックしてみてください!

github.com

ツイッター

引き続きブログの告知が主なつぶやき内容になっていますが、運用中です 💪

twitter.com

振り返り

去年に引き続きフルリモートワークな1年でした。

今年も主に活動はブログでしたが、ポッドキャスト・GitHub Repository の公開など GameWith として新しい取り組みにチャレンジすることができました。

継続的なアウトプットや広報活動を続けることができたのは、技術広報の活動に携わっていただいた方々やブログを見ているユーザー、サービスを利用しているユーザーのおかげです!ありがとうございました!

来年も継続的に、アウトプットしていくのでよろしくおねがいします!

GameWith のログの実装について #GameWith #TechWith

はじめに

こんにちは。GameWith のエンジニアの tiwu です!

今回のブログは普段の開発の際にどのようにログの実装をしているか解説していきたいと思います。

ログの環境周り

GameWith では Google Analytics と タグマネージャーを利用しログの管理を行っています。

tech.gamewith.co.jp

こちらでも触れていますが Google Analytics に送信されたログは BigQuery へエクポートし、DataPortal などを利用し可視化・調査を行っています。

実装例

例1

画面下部にあるこちらのコンポーネントを例に紹介していきます。

f:id:tiwu:20211021180604p:plain

このコンポーネントでは「インプレッション」と「クリック」の2つのログが実装されています。

<div class="walkthrough-recruit-banner gtm-ga4-walkthrough-imp-event" gtm-walkthrough-name="ゲームプレイワーカー" gtm-walkthrough-type="攻略トップ">
    <img src="https://img.gamewith.jp/walkthrough/recruit/banner.png" class="_img" alt="recruit banner">
    <div class="_link">
        <a href="https://recruit-writer.gamewith.co.jp/" class="btn is-accent-nuri btn--full gtm-ga4-walkthrough-click-event" gtm-walkthrough-name="ゲームプレイワーカー" gtm-walkthrough-type="攻略トップ" target="_blank" rel="nofollow">詳細を見る</a>
    </div>
</div>

インプレッションは gtm-ga4-walkthrough-imp-event クラスをつけることで発火し、クラスが付与されているタグの gtm-walkthrough-namegtm-walkthrough-type の値が送信されます。

このインプレッションの処理はタグマネージャーでトリガーを定義しています。

f:id:tiwu:20211021190227p:plain

トリガーのタイプを「要素の表示」にすることでタグマネージャー側でインプレッションの監視を行ってくれるため、実装者が自前でインプレッションの監視処理(例えば Intersection Observer)を書く必要はありません!

クリックは gtm-ga4-walkthrough-click-event クラスを a タグに付与することで a タグを含む a タグ内のタグ全てのクリックで発火し、インプレッションと同様にgtm-walkthrough-namegtm-walkthrough-type の値が送信されます。

このクリックの処理もタグマネージャーでトリガーを定義しています。

f:id:tiwu:20211021190923p:plain

トリガーのタイプを「クリック - リンクのみ」にすることで a タグのクリックの監視を行ってくれます。

※トリガーのタイプを「すべての要素クリック」にすると、a タグ内の別のタグをクリックした際に発火しないので要注意です

実装者はログを送信したい対象に対してクラスなどを付与するだけでログが送信されるため、ログ送信の実装工数はかなり少なくなっています 🎉

例2

ケースによっては JavaScript から任意のタイミングでログを送信したいこともあります。

その場合例1のようにクラスを付与するとタイミングを制御できないのでタグマネージャーでカスタムイベントを作成し、JavaScript から発火させます。

const fireWalkthroughClickEvent = (name, type) => {
  window.dataLayer.push({
    "event" : "Walkthrough_Click_Event",
    "gtm-ga4-walkthrough-name" : name,
    "gtm-ga4-walkthrough-type" : type,
  })
};

例1と別のトリガーを作っていますが、最終的には例1と同じイベント名でログが送信されます。

f:id:tiwu:20211022102304p:plain

例3

GameWith には GameWith Design System という Vue + TypeScript で Web Components を作る仕組みがあり、こちらでもログを送信することができます。

GameWith Design System について詳しくはこちらをご覧ください。

tech.gamewith.co.jp

GameWith Design System は TypeScript で書いているため、下記のような型定義を作り利用しています。

※利用しているトリガーは例2と同じものです

type GTMEvent<T> = {
  event: string;
} & {
  [P in keyof Omit<T, "event">]: P extends `gtm-ga4-${string}` ? T[P] : never;
};

interface GTMWindow extends Window {
  dataLayer: {
    push: (args: unknown) => boolean;
  };
}

declare let window: GTMWindow;

class GA4 {
  public static push<T extends GTMEvent<T>>(args: T): void {
    window.dataLayer.push(args);
  }
}

利用時は下記のようなイメージです。

interface ClickEvent {
  event: "gtm-ga4-walkthrough-click-event";
  "gtm-ga4-walkthrough-type": string;
  "gtm-ga4-walkthrough-name": string;
}

GA4.push<ClickEvent>({
  event: "gtm-ga4-walkthrough-click-event",
  "gtm-ga4-walkthrough-type": "type",
  "gtm-ga4-walkthrough-name": "name",
});

終わりに

インプレッションの監視・クリックによる発火などタグマネージャーに任せることができ、かなり便利と感じます 🎉

ぜひ何かの参考になればと思います!

Twitter

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

twitter.com

Wanted!

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

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

www.wantedly.com

ゲームをより楽しむための攻略ツール開発コンテストを開催! #GameWith #TechWith

はじめに

こんにちは。GameWith のエンジニアの tiwu です!

今回のブログは現在エントリー募集中のツール開発コンテストについて紹介していきます。

ツール開発コンテスト

f:id:tiwu:20211011120826p:plain

この度 GameWith では、ゲーム攻略のためのツール開発コンテストを開催いたします。

最優秀賞の賞金は何と100万円、18歳以上であればどなたでもご応募いただけます!

開催の背景

GameWith は“ゲームをより楽しめる世界を創る”をミッションとして掲げています。

GameWith がユーザーがよりゲームを楽しめるツールを提供するだけでなく、ユーザー自らがゲーム攻略ツールを開発し、それを世の中に公開する機会を提供することで、ミッションの実現を目指しています。

スケジュール

  • エントリー期間:2021/9/17~2021/10/21
  • 応募締切:11/15
  • 結果発表:11月末~12月初頭(予定)

※延長可能性あり

賞金

最優秀賞 100万円

優秀賞  50万円

特別賞  10万円

応募方法・詳細

応募方法や詳細はこちらの特設サイトをチェックしてください!

tool-contest.gamewith.co.jp

GameWith とツール

本ブログでも過去にいくつかツールについて執筆しています!

tech.gamewith.co.jp

tech.gamewith.co.jp

Firebase と GDS(GameWith Design System) により以前と比べ高速な開発が可能になり、現在もいくつかツールを開発をしています 👍

GDS についてはこちらを御覧ください。

tech.gamewith.co.jp

終わりに

「応募したい!」「応募するか悩んでいる…」という方は、まずはエントリーをお願いいたします!

Twitter

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

twitter.com

Wanted!

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

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

www.wantedly.com

コネクテッドシート導入時に意識したい料金について #GameWith #TechWith #ConnectedSheet

はじめに

GameWithディレクター兼分析チームの@fujii86です。 Googleのグループウェア機能の1つ、コネクテッドシートを社内で活用するにあたり、気になる料金面についてまとめていきます。

Connected Sheetとは

Googleが米国時間2020年6月30日「G Suite」ユーザーに向けて一般提供を開始した、Spreadsheet上でBigQueryデータを取り扱うことができるサービスです。
Google WorkspaceのEnterpriseサービスプランでのみ活用が可能となります。

SQLを活用せず、データをインポートするかのようにBigQueryデータを連携し、取得したデータを「グラフ」「ピボットテーブル」「関数」等の形式で集計表示する事が可能です。 ※より高度な事もできます

経緯

GameWIthはGoogle のグループウェア「Google Workspace(旧G Suite)」を活用しておりますが、最新の機能を取り入れ業務の効率化やセキュリティ強化を図る為、先日サービスプランを「Enterprise」へアップデート致しました。

アップデートに伴い多くの機能が追加されましたが、その中にある「コネクテッドシート」は、スプレッドシートでBigQueryデータを関数やグラフ、ピボットテーブルなどを使用して分析できます。ただ注意点として、BigQuery はクエリを実行する度に都度課金が発生するため、仮に全社員が毎日全データを select するといったことが起きてしまうと利用料金が跳ね上がってしまう可能性があります。

そこで、私のほうでコネクテッドシートの料金周りの調査を行う事となり、その調査結果を共有させて頂く形となりました。

結論としてビッグデータを多くの人が活用できるようにするという観点に置いて、とても素晴らしい機能である為、是非読んでる皆様においては料金の発生箇所を把握して運用に踏み出して頂けたら幸いです。

料金に関しての調査結果

料金が発生する場所

  • 連携データから「グラフ」「ピボットテーブル」「関数」「計算された列」「列の統計情報」の適応(作成)をする時
  • 作成した「グラフ」「ピボットテーブル」「関数」「計算された列」「列の統計情報」の更新をする時
  • カスタムクエリでbigqueryと連携をする時

料金が発生しなかった場所

  • データ連携時
  • 異なるアカウントでの接続時
  • Spreadsheet開閉時

料金を軽減する為に意識したい事

  • 更新タイミングをデフォルトの手動同期で活用していくこと
  • 適応又は更新時には表示されている処理量を確認すること
  • カスタムコスト管理の作成を実施すること

補足

  • 自動同期を活用したダッシュボードを作成する場合は、管理が行いやすいデータポータル活用を推奨

コネクテッドシートが有効なケース

  • SQL知識が無くてもビッグデータの分析が行える為、企画職等が直接分析を行いやすい
  • 手動更新が行える為、今後継続して活用するかわからない分析のテスト運用を行いやすい

機能概要

コネクテッドシートの具体的なイメージが沸かないかと思うので、機能の紹介です。
コネクテッドシートは、スプレッドシートから活用することができます。

①データコネクタでbigqueryと連携を行う
指定するテーブルの情報は別途活用者向けにまとめておく必要があります。 f:id:fujii86:20211006190528p:plain

②連携シートから活用したい形式を選択
主にはグラフ、ピボットテーブル、関数を活用していきます。 f:id:fujii86:20211006190935p:plain

③グラフの設定を埋めていく
ピボットテーブルは設定項目が異なり、関数は設定方法が異なります。
下図はグラフの一例となります。 f:id:fujii86:20211006191430p:plain

④データの更新時間の設定を確認する
手動更新を推奨してますが、自動更新についても触れておきます。 f:id:fujii86:20211005161828p:plain

最後に

ここまでお読み頂きありがとうございました。
活用次第では素敵な機能だと感じておりますので、より多くの企業に導入され、より多くの人が活用できることを願っております。

Twitter

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

twitter.com

Wanted!

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

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

www.wantedly.com

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