イントロダクション
Cordova Pluginで自前の処理を組み込みたい場合に利用する、フックスクリプトについてまとめました。
Cordovaプラグインについての記事
- 基本事項
- JavaScript部分の実装
- [Native部分の実装iOS] ... TODO
- [Native部分の実装Android] ... TODO
- フックスクリプト <- この記事
- [プラグインのテスト] ... TODO
plugin.xmlへの記述
フックスクリプトは次のように記述します。
<hook type="after_plugin_add" src="scripts/afterPluginAdd.js" />
<hook type="before_plugin_rm" src="scripts/beforePluginRm.js" />
<hook type="after_plugin_install" src="scripts/afterInstall.js" />
<hook type="before_plugin_uninstall" src="scripts/beforeUninstall.js" />
フックポイント(type)は、公式サイトに一覧があります。https://cordova.apache.org/docs/ja/latest/guide/appdev/hooks/
よく使う上記4つについて説明すると
- after_plugin_add 何かしらプラグインをaddした時に呼ばれる。
- before_plugin_rm 何かしらプラグインをrmする前に呼ばれる。
- after_plugin_install 本プラグインをインストールした時に呼ばれる。
- before_plugin_uninstall 本プラグインをアンインストールした時に呼ばれる。
この他によく使うのは
- before_prepare プリペア前
- after_prepare プリペア後
- before_compile コンパイル前
- after_compile コンパイル後
です。
ディレクティブ
hookディレクティブは、通常は次のようにplugin.xmlのpluginディレクティブ以下に記述します。
<?xml version="1.0" encoding="utf-8"?>
<plugin id="cordova-plugin-my-plugin" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" >
<name>My Plugin</name>
<hook type="after_plugin_add" src="scripts/afterPluginAdd.js" />
<hook type="before_plugin_rm" src="scripts/beforePluginRm.js" />
<hook type="after_plugin_install" src="scripts/afterInstall.js" />
<hook type="before_plugin_uninstall" src="scripts/beforeUninstall.js" />
...
</plugin>
prepareやcompileに関連するhookは、platform以下に記述しても動作しますが、その場合明示的にplatformを指定したprepareやcompileでないと動作しません。
例えば、
<platform name="android">
<hook type="after_prepare" src="scripts/afterPrepare.js" />
</platform>
としてしまうと、
$ cordova prepare android
のときはafterPrepare.js
が実行されますが
$ cordova prepare
ときはafterPrepare.js
が実行されないので注意してください。
srcは、どこに書いてもOKです。hooksディレクトリは、古い仕様のフックスクリプト(plugin.xmlに記述しなくても、Directory名に応じて自動でcordovaによって実行されるフックスクリプト)と紛らわしいので、個人的にはscriptsディレクトリを作成して使うことが多いです。
プラットフォームとの関係
context.opts.platforms
に、フックスクリプトに対する有効なプラットフォームが入ります。
例えば、今、androidとiosがプラットフォームとして登録されていた場合、cordova prepare
を実行すると
context.opts.platforms = [ 'android', 'ios' ]
となります。
ただし、androidとiosが登録されていたとしても、cordova prepare ios
としてiosだけのプリペアを実行すると、iosだけがフックスクリプトに対する有効なプラットフォームとなり
context.opts.platforms = [ 'ios' ]
となります。
そこで、例えば、cordova prepare
であれ cordova prepare ios
であれ、iosを含む時にだけ実行したい場合は、
if (context.opts.platforms.includes('ios')) {
// iosの時の処理
}
のように記述すると良いと思います。
フックスクリプトの書き方
基本は
module.exports = function (context) {
// 処理
};
context
は、Cordovaの情報が入っています。具体的内容はフックポイントによって多少異なりますが、例えば、cordova prepare
であれば
{
hook: 'after_prepare',
opts: { [後述] },
cmdLine: '[現在実行しているcordovaコマンド]',
cordova: { [cordovaオブジェクト] }
}
となります。cordovaオブジェクトは、各種cordovaコマンドを呼び出すためのメソッドから成り立っています。
after_plugin_add
,before_plugin_rm
,after_plugin_install
,before_plugin_uninstall
のフックポイントの場合、今のプロジェクトに特定のプラットフォームがあるかどうかは、次のようなコードで判定出来ます。(他のフックポイントについては未検証なので、各自で調べてみてください)
androidプラットフォームがあるかどうかの確認
if ( context.opts.cordova.platforms.indexOf("android") >=0 ) {
// Androidプラットフォームがある場合の処理
}
context.optsについて
context.optsには、以下のような情報が入っています。
{
"cordova": {
"platforms": [
"android"
],
"plugins": [
"cordova-plugin-whitelist",
"mobi.monaca.plugins.debugger"
],
"version": "6.2.0"
},
"plugin": {
"id": "[プラグインID]",
"pluginInfo": {
"filepath": "[プラグインへのフルパス]",
...
},
"platform": "android",
"dir": "[プラグインへのフルパス]"
},
"projectRoot" : "[プロジェクトへのフルパス]"
}
特に、config.opts.plugin.id
で、今動作しているフックスクリプトのプラグインIDが取得することが出来るのは重宝します。
CordovaConfigについて
contextには、プロジェクトのルートディレクトリの情報は入っていますが、config.xmlに記述されている情報はありません。そこで、ConfigParserを使って以下のように取得します。
var ConfigParser = context.requireCordovaModule('cordova-common').ConfigParser;
var cfg = new ConfigParser("config.xml");
cfgオブジェクトを使い、例えば、アプリのpackageネームを取得したい場合は
var packageName = cfg.android_packageName() || cfg.packageName();
とします。
ConfigParserの詳しい仕様・使い方は
https://github.com/apache/cordova-common/blob/master/src/ConfigParser/ConfigParser.js
を参照してください。
cordova-commonのその他のオブジェクトについては、
https://github.com/apache/cordova-common
を参照してください。
2つほどサンプルを紹介します
1. Preferenceを取得する
config.xmlの中に
<preference name="MY_SETTINGS" value="MY_VALUES" />
のような設定があったとすると、フックスクリプトから次のように取得することができます。
var myValues = cfg.getPreference("MY_SETTINGS");
各プラットフォームごとの設定が欲しい場合は、
var myValues = cfg.getPreference("MY_SETTINGS","android"); // iosの場合、androidの箇所をiosにする
とします。この場合、プラットフォームディレクティブの下に当該preferenceがあればそれを取得し、
なければ、グローバルのに当該preferenceがあればそれを取得するようになります。
2. プラグインのvariablesで設定した設定値を取得する。
$ cordova plugin add sample_plugin --variable HOGE_SDK_VERSION=1234
このような形で設定したvariableは、config.xmlに次のような形式で設定されます
<plugin name="sample_plugin" spec="...">
<variable name="HOGE_SDK_VERSION" value="1234" />
</plugin>
これをフックスクリプトから取得するためには、次のようにします。
var plugin = cfg.getPlugin(context.opts.plugin.id);
if (plugin) {
var variables = plugin.variables;
if (variables) {
var hogeSdkVersion = variables["HOGE_SDK_VERSION"];
// hogeSdkVersionを使った書き込みなど
}
}
これを用いることで、プラグインの中で利用するSDKのバージョンを、プロジェクト側で変更したり
する機能を実装することができます。
やや複雑な処理になりますが、https://github.com/dpa99c/cordova-android-play-services-gradle-release は
このようなやり方でgoogle-play-servicesのバージョンを変更しています。非常に勉強になります。(だだし、このプラグインでは上記のConfigParserは利用せず、自前でconfig.xmlを読み込み、xml2jsでパースしています。)
プロジェクトのプラットフォーム以下にあるJSモジュールを組み込む
pathを解決して、requireすることで利用することが出来ます。例えば、platform/ios以下にあるcordova/API.jsを読み込みたい場合は、次のようにフックスクリプトを実装します。
module.exports = function (context) {
var projectRoot = context.opts.projectRoot;
var path = require('path');
var pathAPI = path.resolve(projectRoot, 'platforms', 'ios', 'cordova', 'Api.js');
var API = require(pathAPI); // platforms/ios/cordova/API.jsを読み込む
var api = new API('ios') // APIのインスタンスを作成する。
console.log(api.locations); // apiのlocationsを表示する。
};
上記フックスクリプトをafter_prepareフックポイントで設定する
<hook type="after_prepare" src="sample_hook.js"/>
ことにより、
次のような値を取得することが出来ます。
{ root: '[プロジェクトへの絶対パス]/platforms/ios',
www: '[プロジェクトへの絶対パス]/platforms/ios/www',
platformWww: '[プロジェクトへの絶対パス]/platforms/ios/platform_www',
configXml: '[プロジェクトへの絶対パス]/platforms/ios/HelloCordova/config.xml',
defaultConfigXml: '[プロジェクトへの絶対パス]/platforms/ios/cordova/defaults.xml',
pbxproj: '[プロジェクトへの絶対パス]/platforms/ios/HelloCordova.xcodeproj/project.pbxproj',
xcodeProjDir: '[プロジェクトへの絶対パス]/platforms/ios/HelloCordova.xcodeproj',
xcodeCordovaProj: '[プロジェクトへの絶対パス]/platforms/ios/HelloCordova',
cordovaJs: 'bin/CordovaLib/cordova.js',
cordovaJsSrc: 'bin/cordova-js-src' }
応用1
Androidのプラットフォームバージョンを取得する
module.exports = function (context) {
var projectRoot = context.opts.projectRoot;
var path = require('path');
var pathVersion = path.resolve(projectRoot, 'platforms', 'android', 'cordova', 'version');
console.log(pathVersion);
var version = require(pathVersion);
console.log(version);
// console.log(context.cordova.projectMetadata.getPlatforms(context.opts.projectRoot));
};
これを、cordova prepareのフックスクリプトとして入れると
{ version: '7.2.0-dev' }
こういう結果を取得することが出来る。
また、
console.log(context.cordova.projectMetadata.getPlatforms(context.opts.projectRoot));
を使った場合、プラットフォームをSEMVER形式で指定して組み込んだ場合は
{ state: 'fulfilled',
value: [ { name: 'android', version: '^7.1.1' } ] }
にようになるが、ファイルパスで指定した場合は
{ state: 'fulfilled',
value: [ { name: 'android', src: 'file:../cordova-android' } ] }
となってしまう。
その他のライブラリの利用
Cordova 6.5以前の場合
公式のやり方は特にないと思います。
自分は、必要なライブラリは、scriptsディレクトリの下にnode_modulesディレクトリを作成して、直接そこに置いています。
Cordova 7.1以後の場合
プラグインにpackage.jsonが必須になったため、プラグイン自体も自身が利用したいJSモジュールをpackage.json経由で組み込むことが出来るようになりました。
プラグインのpackage.jsonに記述したJSモジュールは、フックスクリプトからも利用出来ます。
非同期処理
q.jsを使って行うことが出来る。Qオブジェクトは、requireCordovaModuleを使って取得できる。
module.exports = function (context) {
var Q = context.requireCordovaModule("q");
var deferred = Q.defer();
// 非同期処理
do_something_async(function (result) {
if (result.isSuccess) {
deferred.resolve();
} else {
deferred.reject();
}
});
return deferred.promise;
};
Cordova 9以後では、requireCordovaModule("q");
が使えないかもしれないので、プラグインごとに package.json
に q.js
を組み込むように指定して、フックスクリプト側では
var Q = require('q');
のが良いと思います。あるいは、JSのPromiseをそのまま使ってください。