Help us understand the problem. What is going on with this article?

Cordova Pluginのフックスクリプトについて

More than 1 year has passed since last update.

イントロダクション

Cordova Pluginで自前の処理を組み込みたい場合に利用する、フックスクリプトについてまとめました。

Cordovaプラグインについての記事

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を読み込みたい場合は、次のようにフックスクリプトを実装します。

sample_hook.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をそのまま使ってください。

KNaito
I love JavaScript, Html, PHP, Python, Java, Ruby, Perl, C, C++, Objective-C and Haskell. My stack overflow address is http://stackoverflow.com/users/3535002/knaito
http://d.hatena.ne.jp/knight_9999/
asial
開発中に体験した技術についての情報をつづります
http://www.asial.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away