Edit the Blog Post Header component above this, then place your content here, then fill out the Related Articles section below, if possible. You should have at least one related article and ideally, all three.
Feel free to add supplementary content to the sidebar at right, but please retain the Twitter component (it can live at the bottom of your added content).
This text component can be edited or deleted as necessary.
Related articles only have an image (the squarer version of the banner) and a title. This text can be deleted. Place 1-3 related articles.
「共有すべきか、せざるべきか、それが問題だ」 - シェイクスピアがReact Native開発者であったら、こう言ったでしょう
React Nativeを使用してアプリで作成する場合、おそらくこの点 ☝️ を自問することになるでしょう。Android TV、Apple TV、Fire TVのいずれを対象にしている場合でも、ナビゲーション、状態管理、ビジネスロジック、UIコンポーネントなどは大部分が共通しており、おそらく同じライブラリが使用されています。では、なぜコードベースを個別に維持する必要があるのでしょうか。
Vega OSでは、これを最初から適切に設計する機会が得られました。開発者を特定のターゲットにロックインする新しいプラットフォームを別々に提供するのではなく、複数のTVプラットフォーム向けに同時にビルドしやすくすることが目標の1つでした。アプリのアーキテクチャにもよりますが、複数のTVオペレーティングシステム間でコードベースの70~85%を現実的に共有できます。このブログ記事では、コードベース共有のしくみを解説し、「共有すべき対象とすべきでない対象」を見極める方法を説明します。
さっそく見ていきましょう。
GitHubにいくつかのプロジェクトを公開しています。自身のワークフローに合った方法を選んでそのまま開発を進めることも可能です。この記事を読み進めて、ワークスペースのセットアップ手順や、しくみを支えるツール、さらに、共有できる部分と共有すべきでない部分について理解を深めることもできます。
👋 Multi-TV Hello World - スターターテンプレート
📺 React Native Multi-TV App Sample – 本番環境のリファレンスアプリ
🤖 Vega Multi-TV Migration agent skill - エージェントスキルを使用したAI支援による移行
複数のTVプラットフォーム間でのコードの共有は、実際にどのように行われるのでしょうか。 共有コードは1か所にまとめ、OS固有の機能(ビデオ再生、ネイティブ統合、プラットフォーム特有の挙動)は分離しておく必要があります。
このアプローチを実現するには、VegaプロジェクトをYarn v4ワークスペースを使用したモノレポ構成へリファクタリングする必要があります。Vega側では、Vega SDKをそのまま使用し続けます。ほかのプラットフォームについては、Expo TVを使用します。Expo TVは、Android TV、Apple TV、ウェブを、1つのExpoプロジェクトでサポートします。これらを3つのワークスペースに分割します。
ルートのpackage.jsonによって、これらのサブプロジェクトにまたがるビルド処理を調整して管理します。各ワークスペースはそれぞれ独自の依存関係とビルド構成を保持しますが、ワークスペース間のインポートによって共通コードを共有します
押さえておきたいのは、OS用のパッケージはsharedからインポートできますが、shared側からはOS用パッケージを決してインポートしないという点です。
注: デフォルトではVega SDKはnpmと連携して動作しますが、このモノレポ構成ではYarn v4ワークスペースを使用します。 パッケージマネージャーの競合を回避するために、一貫してYarnのコマンド(yarn installやyarn workspaceなど)を使用してください。
ワークスペース構成によって堅牢な基盤が提供されますが、コードの共有を実現するには、さらに2つのツールが必要不可欠です。
1つ目はVMRP(Vegaモジュールリゾルバーのプリセット)です。sharedのコードは、react-native-gesture-handlerやreact-native-reanimatedなどの標準的なReact Nativeのインポートを使用しますが、Vegaにはこれらのライブラリを移植した独自のライブラリが用意されています。VMRPはBabelプリセットとして動作し、ビルド時にそれらのインポートを自動的にVega版に置き換えるため、sharedのコードはクリーンな状態と移植性が維持されます。
Vegaパッケージのbabel.config.jsでこれを構成します。
これを導入することで、sharedのコード内の「react-native-gesture-handler」は、Vega用にビルドする際に、自動的に「@amazon-devices/react-native-gesture-handler」に置き換えられます。条件付きインポートも、プラットフォームの確認も行われません。
2つ目はVega Studioのモノレポ対応です。プロジェクトを開いたときに、Vega Studioはワークスペースのレイアウトを自動的に検出し、Vegaのサブパッケージを自動で取り込みます。
[Settings] > [Vega] > [Features: Monorepo] で有効にすると、パッケージの検出、ワークスペースの同期、ビルドタスクの調整が自動で処理されます。
すべてのコードがpackages/shared/に入るわけではありません。プラットフォーム間でまったく同じように動くものもあれば、OS固有の実装が必要なもの、その中間に位置するものもあります。どのコードをどこに配置するかを判断するための考え方は次のとおりです。
ここが最も簡単に共有でき、再利用効果が最大になる領域です。API呼び出し、Redux/Zustandストア、データ変換、検証ロジック、フォーマットユーティリティには、通常、OS固有の依存関係はありません。経験則として、ネイティブAPIにアクセスしないコードや、描画処理を伴わないコードはpackages/shared/に配置するのが適切です。
Multi-TV App Sampleでは、これに従って動的コンテンツ読み込みを実装しています。カタログAPIクライアント、データ変換処理、型定義はすべてsharedパッケージ内に配置されており、各プラットフォームは同じデータレイヤーを利用します。
UIコンポーネントの多くも共有できます。ボタン、カード、リスト、レイアウト、モーダル、グリッドビューなどの要素は、通常は標準的なReact Nativeのコンポーネントであり、プラットフォーム間で変更なしに動作します。
興味深い点は、プラットフォーム固有のスタイル設定です。React Nativeのファイル拡張子解決機構によって、これが適切に処理されます。まずBanner.tsxとしてベースコンポーネントを作成した後、Banner.kepler.tsxのようにプラットフォーム固有の拡張子を追加すると、Metroによってビルド時に適切なファイルが選択されます。これは、Metroの構成がモノレポ解決用に設定されていることを前提としており、Hello WorldとMulti-TV App Sampleのリポジトリには、この構成が含まれています。
Hello Worldのリポジトリでは、HeaderLogoコンポーネントでこのしくみを示しています。.kepler.tsx、.android.tsx、.ios.tsx、.web.tsxなどのバリアントを用意することで、さまざまなプラットフォームに応じたロゴを自動的に読み込みます。
個別にファイルを用意するほどではない小さな差異の場合は、Platform.select()を使用することもできます。
TVアプリには2種類のナビゲーションパターンがあります。
画面間のルーティング(ページ遷移、タブ、ドロワーなど)は、通常そのまま共有できます。React Navigation(Vegaではreact-navigationパッケージを介してサポート)を使用している場合は、画面定義、ルート構成、ナビゲーション構造は各プラットフォームで一貫して動作します。
空間/フォーカスナビゲーション(リモコンのD-Padで画面上の要素間でフォーカスを移動する操作)はプラットフォームに固有です。Multi-TV App Sampleでは、React TV Space Navigationを使用してすべてのプラットフォームでのフォーカス移動を制御していますが、その下でリモコンのキーイベントを取得するレイヤーはOSごとに異なります。各プラットフォームで発生するキーイベントが異なるため、RemoteControlManagerには、.android.ts、.ios.ts、.kepler.tsファイルが個別に用意されています。フォーカスロジックは共通化しながら、入力処理を分岐させています。
TVアプリは、さまざまな画面サイズや表示密度で正しく表示される必要があります。ここでのパターンはUIと同じで、デザインシステム(トークン、スペーシング、タイポグラフィスケール)は共有しつつ、プラットフォームごとに調整が必要な部分はプラットフォーム固有ファイルやPlatform.select()を使用して対応します。本番環境のアプリでは、単純なスケーリングに頼るのではなく、レスポンシブレイアウトを備えた適切なテーマレイヤーを構築することを検討してください。
Hello Worldリポジトリには、1920×1080を基準としてTVディスプレイ間で寸法を正規化するスケーリングユーティリティが含まれています。スケーリングロジック自体は共有できますが、セーフエリアやオーバースキャンなど、一部の領域ではプラットフォーム固有の調整が必要になる場合があります。
本番環境のストリーミングアプリでは、最適なパフォーマンスを得るためにOS固有のメディアプレーヤーを使用する傾向があります。OSごとにネイティブ実装を用意することも、react-native-video(現在はVega対応済み)のような抽象化レイヤーを利用して複数のプラットフォームに対応することもできます。このような抽象化を使用しない場合、メディア関連の実装はOS固有パッケージ内に配置する必要があります。
コンテンツランチャー、アプリ内課金、Amazon Device Messagingなどの機能は、基盤となるAPIがOS固有であるため、現時点ではVegaとFire OSで別々の実装が必要です。これらは将来的に単一のRNライブラリに統合していく予定ですが、当面は後で統合しやすいように、これらの実装を構造化しておいてください。プラットフォームパッケージ内に実装を配置し、共有コードから呼び出せるように明確なインターフェイスを提供するようにします。
初期段階で注意しておくべき点として、ButtonやTextのようなシンプルなVUICコンポーネントは、標準的なReact Nativeコンポーネントへの移行が容易です。一方で、CarouselやSeekBarのようなコンポーネントは置き換えにより多くの作業が必要になります。移行途中で想定外の問題に直面しないよう、分析フェーズの段階でこれらの依存関係を特定しておいてください。
直接ネイティブコードにアクセスする要素(カスタムTurboModule、DRM実装、ハードウェア固有の機能)は、プラットフォーム固有にしておく必要があります。これを回避する方法はありません。
実際の動作を簡単に確認するには、Multi-TV Hello Worldリポジトリをお勧めします。このリポジトリには、Vega、Android TV、Apple TV、ウェブのすべてで、同じコードベースから描画される共有Headerコンポーネントを備えたhello-worldプロジェクトが含まれています。
本番環境のアプリにより近い例を確認したい場合は、React Native Multi‑TV App Sampleを参照してください。これは以下の機能を備えたTVアプリ用のテンプレートです。
react-native-videoによるビデオ再生
React TV Space Navigationによる空間ナビゲーション
ドロワーナビゲーション、グリッドレイアウト、動的なヒーローバナー
すべてのプラットフォームに対応したリモコンのサポート
プラットフォーム固有のファイル解決を備えた共有UIライブラリ(@multi-tv/shared-ui)
このサンプルは、1つのモノレポからAndroid TV、Apple TV、Fire TV(Fire OS)、Fire TV(Vega OS)、ウェブをサポートしています。大規模な環境で共有ワークスペースパターンがどのように動作するかを確認するのに有用なリファレンスです。
移行プロセスは3フェーズのエージェントスキルとして実装されており、Kiro、Claudeを含む各種AIコーディングアシスタントで利用できます。GitHubのvega-multi-tv-migrationで公開されています。
Kiroで使用する場合は、vega-multi-tv-migrationディレクトリを~/.kiro/skills/にコピーし、アプリの移行に関する会話を始めるだけです。会話の内容に応じて自動的にアクティブになります。
このスキルは静的コード解析を実行し、推定コード再利用率、依存関係の分類(sharedに配置するもの、OS‑specificに残すもの、VMRPが処理するもの)、画面ごとの移行計画を含むエグゼクティブサマリーを生成します。
「Analyse my Vega app for multi-platform migration.(マルチプラットフォームに移行できるようにVegaアプリを分析してください。)」
このフェーズでは、モノレポの基盤を作成し、コードをsharedパッケージとvegaパッケージに移動します。Metroをモノレポ解決に対応させ、さらにVMRPをセットアップします。この手順の完了後もVegaアプリは従来どおりビルドして実行できますが、sharedコードが適切に分離された状態になっています。
「Convert my Vega project to a yarn workspaces monorepo using the analysis from Phase 1.(フェーズ1の分析に基づいて、Vegaプロジェクトをyarnワークスペースのモノレポに変換してください。)」
このフェーズでは、Expo TVパッケージをセットアップし、Vega固有の依存関係に対する代替実装を追加します。
「Add Android TV and Apple TV support using Expo TV.(Expo TVを使用してAndroid TVとApple TVのサポートを追加してください。)Replace my Vega-specific dependencies with stock React Native equivalents.(Vega固有の依存関係は標準的なReact Nativeの代替実装に置き換えてください。)」
既に複数のパートナー企業がVegaを活用してマルチプラットフォームTVアプリを構築し、実際に成果を上げています。開発者向けコミュニティでは、React Native VegaのコードをTVオペレーティングシステム間で再利用する方法を確認できます。また、マルチプラットフォームにおけるVegaによるZattooのTVアプリ開発の効率化についての記事も公開されています。これらのケーススタディは、共有ワークスペースアプローチが本番環境でどれほど実用的であるかを示しています。
実際の移行作業を通じて得られた教訓をいくつか紹介します。