ターボモジュールの作成
ターボモジュールは、スタンドアロンライブラリで作成することも、アプリパッケージの一部として作成することもできます。これらをそれぞれ「スタンドアロン」と「アプリ内」のターボモジュールと呼びます。
Vega SDKには、スタンドアロンライブラリでターボモジュールを作成するためのテンプレートが用意されていますが、プロジェクトに最適な方法を選択できます。以下の手順では、両方の方法を説明します。完了すると、ディレクトリ構造は次のようになります。
[プロジェクトのルートディレクトリ]/
├─ kepler/
│ ├─ turbo-modules/
│ │ ├─ generated/
│ │ │ ├─ (C++ specifications)
│ │ │
│ │ ├─ (C++ implementations)
│ │
│ ├─ AutoLinkInit.cpp (autolink setup)
│
├─ src/
│ ├─ turbo-modules/
│ │ ├─ (TypeScript interface files)
│ │
│ ├─ App.tsx (only for in-app Turbo Modules)
│
├─ tst/
│ ├─ (JS test files)
│
├─ .eslintrc
├─ .prettierrc
├─ babel.config.js
├─ CMakeLists.txt
├─ jest.config.json
├─ metro.config.js
├─ package.json
├─ react-native.config.js
├─ tsconfig.json
├─ tsconfig-build.json
セットアップ
アプリ内ターボモジュール
アプリ内モジュールの場合は、アプリの作成の手順に従って開始アプリを作成します。
スタンドアロンターボモジュール
スタンドアロンモジュールの場合は、ターボモジュールテンプレートから開始できます。パッケージのルートから以下のコマンドを実行すると、使用可能なテンプレートのリストが表示されます。
kepler project list-templates
ターボモジュールの場合は、次のいずれかを選択してプロジェクトを生成します。
idl-turbo-module: OS API(IDL)を使用している場合basic-turbo-module:独自のコードからターボモジュールを作成する場合
コマンドが成功すると、出力ディレクトリには新しいターボモジュールプロジェクトが含まれます。
IDLターボモジュール
kepler project generate -t idl-turbo-module -n <CamelCaseProjectName> --idlPackage <ReverseDNSNameForIDLPackage>
IDLターボモジュールのテンプレートは、次の追加引数を受け入れます。
-
--idlPackage: ターボモジュールで使用するIDLパッケージ。逆引きDNS表記で記述します。たとえば、com.amazondeveloper.kepler.fooのように指定します。 -
--outputDir:[任意] プロジェクトを生成するディレクトリ。指定しない場合、コマンドを実行したフォルダにプロジェクトが生成されます。
テンプレートによって生成されたプロジェクトでは、指定されたOKIDLインターフェイスへの依存関係だけが宣言されます。インターフェイスを機能的に使用するには、開発者がターボモジュールC++実装ファイルのコードを変更する必要があります。ただし、これらの変更を行わなくても、テンプレートからインターフェイスが生成されると、プロジェクトはリクエストされたインターフェイスを検出して正常にビルドします。
例
kepler project generate -t idl-turbo-module -n TestIdlTm --idlPackage com.amazon.kepler.i18n.text.message_format_classic --interfacePackage KeplerI18nLibraryInterface
プレーンターボモジュール
kepler project generate -t turbomodule -n <CamelCaseProjectName> --idlPackage <ReverseDNSNameForIDLPackage>
プレーンターボモジュールのテンプレートは、次の追加引数を受け入れます。
--outputDir:[任意] プロジェクトを生成するディレクトリ。指定しない場合、コマンドを実行したフォルダにプロジェクトが生成されます。
ターボモジュールの作成
手順1: TurbomoduleAPIをインストールする
テンプレートを使用してターボモジュールを作成した場合、@amzn/keplerscript-turbomodule-apiパッケージは既にpackage.jsonのdependenciesセクションにリストされているため、この手順はスキップできます。
パッケージのルートから、次のコマンドを実行します。
npm install --save @amzn/keplerscript-turbomodule-api
手順2: TypeScriptでインターフェイス定義を作成する
ターボモジュールのテンプレートを使用した場合、このファイルは既に存在しますが、必要に応じて変更する必要があります。NativePinger.tsファイル(注: ファイル名はテンプレートに渡した名前に基づいて付けられます)は、ネイティブモジュールのJavaScriptへのインターフェイスを定義します。慣例により、これはsrc/turbo-modulesに配置されています。
インターフェイスは次のようになるはずです。
import type {VegaTurboModule} from '@amzn/keplerscript-turbomodule-api';
import {TurboModuleRegistry} from '@amzn/keplerscript-turbomodule-api';
export interface Pinger extends KeplerTurboModule {
ping: () => number;
}
export default TurboModuleRegistry.getEnforcing<Pinger>('Pinger');
前述のコードではPingerというインターフェイスを定義し、このインターフェイスは数値を返すpingという単一の関数を公開します。使用可能な戻り値の型の詳細については、ネイティブコードでのJavaScriptの型を参照してください。
TurboModuleRegistry.getEnforcing は'Pinger'をルックアップキーとして使用してモジュールのネイティブオブジェクトをロードし、見つからない場合は例外を発生させます。次に、返されたオブジェクトにPingerインターフェイスをバインドします。
TurboModuleRegistryで使用する文字列と一致する必要はありません。また、後で使用するC++クラス名やファイル名と一致する必要もありません。次のステップのCodegenツールは、一致するエントリをC++スタブコードに生成します。ただし、この名前は後で自動リンクの構成でもう一度使用する必要があります。手順3: Codegenを実行してネイティブスキャフォールディングを生成する
TurbomoduleAPIライブラリには、TypeScriptインターフェイスに基づいてC++スキャフォールディングを生成できるCodegenツールが含まれています。生成されたコードは、ターボモジュールのC++仕様を定義します。Codegenを使用するには、次のコマンドを実行します。
npx keplerscript-turbomodule-api codegen src/turbo-modules/<NativePinger.ts> \
-o vega/0.21/turbo-modules/ \
--new \
--namespace PingerTurboModule
実装なしのエラーをスローします。Codegenは一度に1つのTypeScriptインターフェイスのみを受け入れます。同じパッケージで複数のターボモジュールを作成している場合は、異なる引数を使用してCodegenを複数回実行できます。
Codegen機能の詳細については、npx keplerscript-turbomodule-api codegen -hを実行するか、Vega Codegenに関するよくある質問(FAQ)トピックを参照してください。
手順4: C++関数を実装する
通常、kepler/turbo-modules/generated/ディレクトリにあるCodegenによって作成されたファイルを手動で変更する必要はありません。変更すると、Codegenを再実行したときにこれらのファイルが上書きされることに注意してください。更新内容が失われないようにするには、変更したファイルを別の場所にキャッシュし、codegenを再実行した後にその変更内容をもう一度適用してください。
kepler/turbo-modules/ディレクトリにあるC++(.h/.cpp)ファイルに、インターフェイスを実装します。前のステップで生成された関数本体を入力する必要があります。
// kepler/turbo-modules/Pinger.cpp
double Pinger::ping() {
return 0;
}
TurboModuleRegistryキーとは異なる名前のターボモジュールのテンプレートを使用した場合は、TestIdlTm.h/cppなどのkepler/turbo-modulesにその他の既存のファイルが存在します。これらを削除して、Codegenで作成したファイルを優先することができます。IDL
ターボモジュールがIDL経由でOS APIを使用する場合、インポートを追加し、IDLコンポーネントをインスタンス化し、必要に応じてAPIを使用する必要があります。
例
インポートを追加します。
// kepler/turbo-modules/Pinger.h
// IDLフレームワークヘッダー(APMF)をインポートします。
#include <apmf/apmf_base.h>
#include <apmf/ptr.h>
#include <apmf/process.h>
...
class Pinger: public com::amazon::kepler::turbomodule::VegaTurboModule {
public:
...
// メンバー変数を追加します
apmf::Ptr<apmf::iface::com::amazon::apmf::IStandard> component;
...
}
IDLコンポーネントをインスタンス化し、APIを使用します。
// kepler/turbo-modules/Pinger.cpp
// 使用するIDLパッケージをインポートします。
// IHello.hは一例であり、存在しない可能性が高いことに注意してください。
#include <apmf/iface/com/amazon/kepler/foo/IHello.h>
using apmf::iface::com::amazon::kepler::foo::IHello;
...
Pinger::Pinger() {
// コンストラクターで「コンポーネント」を初期化します
auto const process = apmf::GetProcessObject();
this->component = process->getComponent("/com.amazondeveloper.kepler.foo");
}
...
//「コンポーネント」を使用してAPIにアクセスできます。
// 以下の呼び出しはいつでも利用できるとは限らないため、あくまでも一例であることに注意してください。
auto const hello = this->component.TryQueryInterface<IHello>();
return hello->getHelloString();
...
com.amazondeveloper.kepler.fooは、テンプレートに渡した--idlPackage(存在する場合)と一致している必要があります。
より詳細な例については、インターナショナリゼーション開発者ガイドを参照してください。
手順5: JavaScriptレイヤーを実装する
作成したTypeScriptインターフェイスは、ネイティブオブジェクトをJavaScriptから呼び出すための型情報を提供します。エラー処理、JavaScriptロギング、オプションパラメーターやサポートされていない型の引数の前処理などの操作では、ネイティブメソッドよりもJavaScriptロジックのレイヤーを追加したい場合があります。現時点ではそのようなロジックが必要ない場合でも、JavaScriptとネイティブAPIを切り分ける基本的なラッパーを追加すれば、今後のAPIの変更に柔軟に対応できるようになります。
import PingerModule from './NativePinger';
class Pinger {
// これはネイティブモジュールに転送するだけですが、さらに複雑な操作もできます。
// または、ネイティブモジュールを必要としないJS専用のユーティリティ関数を入力することもできます。
ping(): number {
return PingerModule.ping();
}
}
export default new Pinger();
その場合は、ターボモジュールのユーザー用にラッパーのインスタンスをエクスポートして、ユーザーとネイティブオブジェクトとの間のレイヤーとして使用します。例えば、スタンドアロンのターボモジュールパッケージでは、index.tsにexport {default as Pinger} from './turbo-modules/Pinger'が含まれている場合があります。
JavaScriptテストを追加する
Jestなどのフレームワークを使用して、JavaScriptコードをテキスト化することができます。Jestでテストを実行する場合、C++ターボモジュールインスタンスは存在しないため、JavaScriptテスト用にモックを作成する必要があります。
次の例は、C++ターボモジュールインスタンスを模倣する方法を示しています。
// テストファイルでは、
jest.mock('<path-to-NativePinger.ts>');
ターボモジュールの手動モックを、モジュールに隣接する__mocks__サブディレクトリ(src/turbo-modules/__mocks__/NativePinger.tsなど)に追加できます。
ターボモジュールのコンシューマーにモックを提供して、独自のテストを円滑に行えるようにすることができます。例えば、JestのsetupFilesに渡すことができるモックファイルを提供することができます 。
手順6: ビルド構成を追加する
テンプレートからターボモジュールを生成した場合、この手順はすでに完了しているはずです。ただし、テンプレートによって提供されるコードを調整したり、追加のCodegenパラメーターを指定したりした場合は、この情報が役立つ場合があります。
自動リンク
Vegaターボモジュールは自動リンクされています。つまり、ネイティブモジュールはアプリによって直接リンクされるのではなく、アプリの依存関係とパッケージ構成に従ってVega向けReact Nativeによってロードされます。ターボモジュールの開発者は、ターボモジュールの登録と構成コードを追加する必要があります。
手順1: AutoLinkInit.cppにモジュールを登録する
kepler/AutoLinkInit.cppには、お使いのターボモジュールに合わせて調整された次のコードがあるはずです。
#include <Vega/turbomodule/VegaTurboModuleRegistration.h>
// モジュール実装用のヘッダーファイル
#include "turbo-modules/Pinger.h"
extern "C" {
__attribute__((visibility("default"))) void
autoLinkVegaTurboModulesV1() noexcept {
KEPLER_REGISTER_TURBO_MODULE(
PingerTurboModule, /* 名前空間 */
Pinger /* クラス名 */
);
// パッケージに複数のターボモジュールが含まれている場合は、ここで登録を続けてください。
}
}
名前空間とクラス名はCodegenに渡される引数と一致しなければなりません。クラス名を渡さなかった場合、CodegenはTurboModuleRegistry.getEnforcingの文字列を使用するため、上記のようにKEPLER_REGISTER_TURBO_MODULE(namespace, classname)を使用できます。TurboModuleRegistryが使用する文字列とは異なるクラス名を渡した場合は、KEPLER_REGISTER_TURBO_MODULE_WITH_NAME(TurboModuleRegistry namestring, namespace, classname)マクロを使用できます。
KEPLER_REGISTER_TURBO_MODULE(
"myName", /* TurboModuleRegistryの文字列 */
PingerTurboModule, /* 名前空間 */
PingerModule /* クラス名 */
);
手順2: react-native.config.jsにメタデータを追加する
すべてのターボモジュール型で、react-native.config.jsに自動リンク構成を指定します。
スタンドアロンのターボモジュールライブラリの場合
module.exports = {
dependency: {
platforms: {
kepler: {
autolink: {
"com.companyname.pinger": { // この名前はメタデータの自動リンクにのみ使用されます。
// 命名の競合を避けるため、リバースDNSの使用をお勧めします。
"libraryName": "libPinger.so", // これはCMakeListsのライブラリ名に由来します。
"provider": "application",
"linkDynamic": true,
"turbomodules": ["Pinger"] // これはTurboModuleRegistryに由来します
//ターボモジュールが複数ある場合は、このリストを展開してください。
}
},
},
},
},
};
アプリ内ターボモジュールの場合
{
...
"kepler": {
"keplerInitializationSharedLibrary": "com.companyname.pinger",
"autolink": {
"com.companyname.pinger": {
"libraryName": "libPinger.so",
"provider": "application",
"linkDynamic": true,
"turbomodules": ["Pinger"]
}
}
...
}
}
動的リンク
linkDynamicは、自動リンクライブラリをアプリの起動時に読み込むか(起動レイテンシーが増加する)、JavaScriptによってターボモジュールがリクエストされたときにロードするかを制御します。ライブラリの設定に関係なく、アプリは独自のreact-native.config.jsの値をニーズに合った値にオーバーライドすることができます。わからない場合は、デフォルトをtrueに設定してください。
CMakeLists.txt
CMakeLists.txtファイルには、プロジェクトのビルドプロセスを定義し、CMakeがさまざまなコンパイラやIDE用のメイクファイルまたはプロジェクトファイルを生成するために使用するコマンドと命令が含まれています。プロジェクトルートには、次のようなCMakeLists.txtがあるはずです。
cmake_minimum_required(VERSION 3.19)
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
project(pinger-module # これはライブラリ名です
バージョン1.0
LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# その他のヘッダーファイルをここに追加
set(HEADERS
kepler/turbo-modules/generated/PingerSpec.h
kepler/turbo-modules/Pinger.h
)
# その他のソースファイルをここに追加
set(SOURCES
kepler/turbo-modules/generated/PingerSpec.cpp
kepler/turbo-modules/Pinger.cpp
kepler/AutoLinkInit.cpp
)
find_package(turbomoduleAPI CONFIG REQUIRED)
kepler_add_turbo_module_library(pinger-module ${HEADERS} ${SOURCES})
# IDL用
# find_package(Apmf REQUIRED)
# find_package(Vega REQUIRED)
#
# kepler_add_idl_import_library(
# idl_pkg
# PACKAGES
# <idlPackage> # com.amazon.kepler.i18n.text.message_format_classicなど
# # IDLパッケージをここに追加
# )
#
# )
# target_link_libraries(pinger-module PRIVATE idl_pkg)
install(TARGETS pinger-module)
この例では、プロジェクトのすべてのソースに1つのCMakeLists.txtファイルを使用していますが、プロジェクトのニーズに合わせて分割することもできます。
ビルドターゲティング
アプリ内ターボモジュールの場合、ビルドターゲティングはアプリのセットアップ手順に従って行う必要があります。
ビルドターゲティングは package.jsonまたはCLIビルドフラグで指定できます。
手順7: ターボモジュールをビルドする
npm install
npm pack
package.jsonスクリプトを微調整して、チームでよく使用されるコマンドを実行したり、既存のスクリプトを段階的に実行したりできます。
手順8: 新しいターボモジュールをアプリで使用する
アプリ内ターボモジュールの場合、相対パスを使用してモジュールをインポートし、標準のJSモジュールと同じように呼び出すことができます。
import Pinger from ‘<path-to-Pinger.ts>’;
Pinger.ping();
スタンドアロンのターボモジュールライブラリの場合は、まずライブラリパッケージをアプリにインストールする必要があります。npm packによって生成された.tgzファイルをインストールするか、yarn workspacesやシンボリックリンクなどのセットアップを使用して、レジストリに公開せずにターボモジュールライブラリを消費することができます。
npm install --save <path-to-pinger.tgz / path-to-Pinger-package / ‘@amzn/pinger’>
次に、標準のJSパッケージと同じようにインポートして使用します。
// ターボモジュールがデフォルトのエクスポートの場合
import Pinger from ‘pinger’;
// ターボモジュールが名前付きエクスポートの場合
import { Pinger } from ‘pinger’;
Pinger.ping()
Jestでアプリのテストに合格するには、ネイティブのターボモジュールを模倣する必要があります。
jest.mock(‘@amzn/pinger/dist/turbo-modules/NativePinger’);
再ビルド
npm packによって生成された.tgzファイルを使用してスタンドアロンのターボモジュールをアプリにインストールし、後でターボモジュールに変更を加える場合は、ターボモジュールを再ビルドした後に新しい.tgzを再インストールする必要があります。
このプロセスは通常、次のとおりです。
- ターボモジュールコードの変更
- ターボモジュール(npmパック)の再ビルド
- アプリパッケージから
npm instal <.tgzへのパス>を実行する - アプリを再ビルドします。
アプリがシンボリックリンクを介してスタンドアロンのターボモジュールを使用している場合(つまり、依存関係がnpm install <TMパッケージへのパス>を介して追加されたか、アプリのpackage.jsonが.tgzへのパスの代わりにディレクトリパスを表示している)、またはyarn workspacesのようなセットアップを使用している場合は、ターボモジュールからアプリに変更を伝播するための再インストール手順は必要はありません。
関連トピック
Last updated: 2025年10月1日

