KARTEを支える2つのビルドプロセス
KARTEは、サイトやアプリの訪問者の行動や感情をリアルタイムに解析し、一人ひとりに合わせたアクションを表示、CX向上を目指すプラットフォーム。
KARTEの基本的な仕組みとしては、Webサイトにタグ(スクリプト)を貼っておくと、サイト訪問者の閲覧をきっかけにスクリプトが実行されて、どのページが参照されていたかなどの情報がKARTEの解析サーバーに送信される。過去の閲覧履歴なども合わせて解析することで、サイト訪問者に合わせたアクションが実行される。
このアクションのためのコードは「KARTEアクションエディタ」で編集される。エディタのGUIでアクションの見た目を編集して保存すると、変更されたソースコードがビルドサーバーに送信され、そこで実行コードに変換された後に、アクションストレージ(CDN)に保存される。ユーザー体験の最適化という視点では、ビルドした実行コードがエンドユーザー(サイト訪問者)のブラウザで素早く表示されることを優先している。
またアクションエディタにはアクションの挙動のプレビュー機能もあるため、ビルドはエディタが動いているブラウザ内でも行われている。編集するたびにエディタでビルドしてプレビューを更新することになるため、ここでのビルド時間が長いと編集体験が悪くなる。そのため編集体験の最適化という視点では、ビルドにかかる時間を短縮することが重要になる。
アクションエディタには、同じソースコードをもとにした実行用とプレビュー用で2つのビルドステップがあり、それぞれビルドの生成物が異なる。このビルドステップでは、「TypeScriptをトランスパイルしてJavaScriptにする」「インポートしているパッケージをnpmから取得する」「ファイルをまとめて1つの実行可能なファイルにする」「サイズを減らすためのminify処理」などの工程も含まれる。こうしたステップにおいて、アクションエディタではrollup.jsを使用している。
ユーザー体験と編集体験の両立を図るなかで、ポイントとなるのがソースコードの作り方だ。エディタで編集するコードは配信ごとに変わるものだが、ポップアップ表示などで共通で利用できるコードも多くあり、そのようなコードはSDKとして分離してビルドすることで、最適化が可能となる。
ビルドフロー全体をあらためてまとめると、コードの中から共通するものはSDKとして分離し、後続処理のために実行用とプレビュー用それぞれでビルドしてある。また、エディタで編集されるソースも実行用とプレビュー用でそれぞれビルドされる。先述の通り実行用は実行速度の最適化を重要視しており、プレビュー用はビルド時間の最適化を重要視している。
ユーザー体験を向上させる実⾏コード⽣成ビルド、どう改善する?
2種類あるビルドを、それぞれいかに最適化していくか。実行用コードのビルドでは、実行速度の最適化を重要視している。実行速度はスクリプトのファイルサイズが影響するので、ファイルサイズを小さくすることを目指している。
実行用コードのビルドのポイントは、ビルド処理の中で不要なコードを削減できるようにすること。SDKのコードはビルド時にtree-shakingされやすいように記載しておく。例えば、コードの中にはプレビュー時の時のみなど特定の条件のみで動くコードがあり、そうした実行時に不要なコードはtree-shakingで削除されるようにする。また、インポート単位を細かく区切ることで、インポートするコード量を減らす。
もう1つ、実行用コードのビルドでは、描画の処理にSvelteライブラリを使い、事前にビルドすることで、重いランタイムコード不要で実行できるようにしている。
実は、以前のアクションではVue.jsを使用していた。Vue.jsではWeb標準であるHTML風に記述できるので、マーケターなど非エンジニアでも書きやすいのがメリットだった。しかし実行時ランタイムが必要で実行時の処理が重いというデメリットがあった。その点、SvelteはVue.jsと同様にHTML風に記述できて、その上事前ビルドすることでランタイム不要となるため軽量になる。今回のアクションでの要件にマッチしていたため採用となった。
編集者体験を向上させるプレビュービルド、どう改善する?
次はプレビュー用のビルドだ。こちらはエディタでの変更の反映を早くするため、ビルド時間の最適化を重要視している。ビルド時間を早くするには、ビルド処理の中での不要な変換を減らすことがポイントとなる。また、ビルドするためのコード自体もブラウザにロードして使うため、なるべくサイズが小さいパッケージやrollup pluginを利用するようにしている。
不要な変換を減らすために有効だったのが「全更新ではなく差分更新」をすることだ。プレビュー用のSDKのビルドでは、後行程で差分更新できるようにするための対応を盛り込んでいる。この対応が、SvelteのHydration機能だ。Hydration機能は通常ではSSR(サーバーサイドレンダリング)で使われるのだが、ここではエディタ上のビルドプロセスをサーバーと見なして動かすことで、ブラウザ内でのビルドでもHydration機能が使えるようにした。Hydration機能でDOMの変更だけを動くようにして高速化を実現している。これを有効にするにはプレビューSDKのビルド時にHydrationの設定が必要になる。
加えてプレビュービルドでは、不要な処理を避けるためにminifyのような処理は避ける。またソースコードが小さいパッケージを使うためにTypeScriptのトランスパイルにはsucraseというパッケージを用いる。またJSONを使う場合、よく使われる@rollup/plugin-jsonのようなプラグインではなく、ブラウザ標準のJSON.parser/JSON.stringifyなどの関数を利用する。
さらに、多くのnpmのパッケージはESM対応されておらず、ブラウザで利用しようとすると変換が必要になる。ブラウザ内で変換すると時間がかかってしまうため、ESM対応しているプロキシサービス経由でパッケージを取得している。
こうしてプレビュー用のビルドではSvelteのHydrationで差分更新し、余計な処理は行わずブラウザの標準関数を使用し、ESM対応のプロキシからパッケージを取得することでビルド時間の短縮を目指す。
2つのビルドをいかに両立させたか
これまでブラウザでの実行速度高速化(ユーザー体験の最適化)と、エディタでの編集速度高速化(編集体験の最適化)のための工夫を述べてきた。ここからは、これら2つのビルドを両立するためのアーキテクチャを解説する。
ビルドが2つあることで、実行時とプレビュー時で挙動に違いがでてしまう課題が生まれていた。内部で生成されるJavaScriptのコードは違っていてもよいが、挙動が違うのでは困る。プレビューではビルドできるものの、実行のためのビルドではエラーになることもあった。
ずれが生じる原因はパッケージバージョンのずれや、Svelteの影響である。対策として、まずビルド処理を単一のパッケージにまとめた。処理が同じで設定だけ違うインターフェースをそれぞれ用意して、2つのビルドで同じ処理を通すようにする。これでライブラリバージョンや環境差分を抑えられる状態となった。
さらに、同一のソースコードから複数の実行コードを生成しないといけないが、同じコードを使うとインポート先が固定になってしまう。実行時には実行時のSDK、プレビュー時にはプレビューSDKを使用し、それぞれ別のSDKを利用したい。そこでplugin-aliasを活用し、ビルドプロセスにより読み込むパッケージやSDKを切り替える。これにより開発用のSDKの切り替えも可能となった。
まとめると、実行速度を優先する実行用のビルドではtree-shakingとSvelteを活用し、ビルド時間を優先するプレビューのビルドでは全更新ではなく差分更新することと不要な処理を省くようにする。そして2つのビルドで不整合が生じないようにするため、ビルドパッケージを共通化し、SDKを切り替えられるようにした。このような工夫により、KARTEではユーザーと編集者双方の体験を最適化する。
片居木氏は最後に、「データを使って、より良い顧客体験を提供することが我々のミッション。より良い顧客体験のためのフロントエンドを作ることを重視している」とセッションを締めくくった。