前言

最近在开发angular工程的自定义插件api以实现cordova插件的调用时,发现网上的教程几乎搜不到angular关于自定义插件api开发的文章,然后发现可以参照@ionic-native的插件代码结构整出来,于是就模仿着弄,搞了个简单的脚手架,然后折腾了一个周末总算弄出来了,过程还是有曲有折,故发文记录分享

1 介绍

本文将细致讲解cordova插件的创建、编写、发布到自定义@ionic-native/plugin的创建、发布过程,以及自定义ionic-native的脚手架开发、发布过程,还有angular项目中如何通过调用自定义@ionic-native/plugin来达到调取cordova插件的具体流程。

2 cordova插件开发

2.1 创建Cordova项目

创建之前确保安装Cordova

cordova create CordovaProject io.cordova.hellocordova CordovaApp
CordovaProject               是创建应用程序的目录名称。
io.cordova.hellocordova      是默认的反向域值。 如果可能,您应该使用您自己的域值。
CordovaApp                   是您应用的标题。

本人在jobProject下创建 CordovaProject

$ cordova create CordovaProject com.ths.ll 思路提示框插件

2.2 安装依赖plugman

plugman是用于安装和卸载用于Apache Cordova项目的插件的命令行工具。
进入CordovaProject项目目录,安装plugman

$ cd ./CordovaProjectPlugins
$ npm install -g plugman

2.3 创建插件

2.3.1创建一个最简单的Toast插件

plugman create --name [插件名] --plugin_id [插件id] --plugin_version [插件版本]

为了方便管理,将插件创建在 Cordova 项目目录下的 plugins 文件夹下

$ cd plugins
$ plugman create --name ThsToast --plugin_id cordova-plugin-ths-toast --plugin_version 1.0.0


接着手动将ThsToast目录重命名为上述plugin_id的值cordova-plugin-ths-toast,这里以及上面的ths表示的是公司的统一插件开发前缀,通常是英文字符串

进入插件目录,添加插件支持的平台环境

$ cd cordova-plugin-ths-toast
$ plugman platform add --platform_name android
$ plugman platform add --platform_name ios

添加之后将在cordova-plugin-ths-toast目录下产生android和ios两个目录,此处只定义android环境的ThsToast,
生成的文件内容如图所示


注意:起名不要和安卓原生方法冲突了,比如这里ThsToast如果改成Toast,就会和android.widget.Toast中的Toast类重名,导致构建报错


2.3.2 插件配置

添加完平台后,cordova-plugin-ths-toast 目录下的 plugin.xml 文件将添加如下内容

修改 plugin.xml 文件内容如下

<?xml version='1.0' encoding='utf-8'?>
<plugin id="cordova-plugin-ths-toast" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
    <name>Toast</name>
    <js-module name="ThsToast" src="www/ThsToast.js">
        <!-- target修改 -->
        <clobbers target="ThsToast" />
    </js-module>

    <platform name="android">
        <config-file parent="/*" target="res/xml/config.xml">
            <feature name="ThsToast">
            <!-- param value修改 -->
                <param name="android-package" value="org.apache.cordova.thstoast.ThsToast" />
            </feature>
        </config-file>
        <config-file parent="/*" target="AndroidManifest.xml" />
        <!-- target-dir修改 -->
        <source-file src="src/android/ThsToast.java" target-dir="src/org/apache/cordova/thstoast" />
    </platform>

    <platform name="ios">
        <config-file parent="/*" target="config.xml">
            <feature name="ThsToast">
                <param name="ios-package" value="ThsToast" />
            </feature>
        </config-file>
        <source-file src="src/ios/ThsToast.m" />
    </platform>
</plugin>

修改www/ThsToast.js,顺带提一下其中exec方法就是调用cordova插件的原始方法,该方法传的'ThsToast','show'和[arg0],success,error参数对应的分别是android/ThsToast.java中的class类名,action和args,callbackContext.success,callbackContext.error



修改 android/ThsToast.java 文件,

2.3.3 初始化插件

npm init

提示的时候name输入插件id,其余根据提示填写,不清楚就直接按回车到结束,将创建一个 package.json 文件

{
  "name": "cordova-plugin-ths-toast",
  "version": "1.0.0",
  "description": "show toast",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/qtpalmtop/cordova-plugin-ths-toast.git"
  },
  "author": "lilin",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/qtpalmtop/cordova-plugin-ths-toast/issues"
  },
  "homepage": "https://github.com/qtpalmtop/cordova-plugin-ths-toast#readme"
}

接着修改package.json,keywords关键字配置是为了在Cordova Plugin Search中显示插件,engines配置是插件可能会列出多个发行版的依赖关系,以便在Cordova CLI选择要从npm获取的插件版本时向其提供指导,旨在最终替换plugin.xml中的engine元素。
详细内容请参考cordova创建插件

{
  "name": "cordova-plugin-ths-toast",
  "version": "1.0.0",
  "description": "show toast",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/qtpalmtop/cordova-plugin-ths-toast.git"
  },
  "author": "lilin",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/qtpalmtop/cordova-plugin-ths-toast/issues"
  },
  "homepage": "https://github.com/qtpalmtop/cordova-plugin-ths-toast#readme",
  "keywords": [
    "ecosystem:cordova",
    "cordova-android",
    "cordova-ios"
  ],
  "engines": {
    "cordovaDependencies": {
      "2.0.0": {
        "cordova-android": ">=3.6.0"
      },
      "4.0.0": {
        "cordova-android": ">=3.6.0",
        "cordova-windows": ">=4.4.0"
      },
      "6.0.0": {
        "cordova": ">100"
      }
    },
    "node": ">=6.0.0"
  }
}

2.3.4 发布插件

发布后就可以正常的通过cordova plugin add cordova-plugin-ths-toast在项目中通过ThsToast.show()使用,但是要在angular项目中使用还需要我们开发自定义插件api,下面我们开始ionic-native的api模块开发

cd cordova-plugin-ths-toast
npm login
npm publish

3 创建ionic-native插件

使用过@ionic-native库的同学肯定都知道,@ionic-native库可以直接导入angular项目中,使用起来也非常方便,只需要在app.module.ts中导入api

// app.module.ts
import { SplashScreen } from '@ionic-native/splash-screen/ngx';

@NgModule({
...,
providers: [
    ...,
    SplashScreen
  ]
})

然后在使用的模块中导入sdk就能直接调用起cordova插件功能

// app.component.ts
import { SplashScreen } from '@ionic-native/splash-screen/ngx';

export class AppComponent {
    constructor(
        private splashScreen: SplashScreen
    ) {
        this.splashScreen.hide();
    }
}

那么要如何才能创建出@ionic-native库一样的插件呢?

3.1 @ionic-native插件代码拆解

首先我们安装下@ionic-native/splash-screen

npm install @ionic-native/splash-screen

可以看到插件结构如下


index.js

分析:__extends函数功能可以理解为是继承不再详细阐述,左边的参数对象会继承右边的对象,SplashScreenOriginal里新建了个函数SplashScreenOriginal,__extends(SplashScreenOriginal, _super)让该对象继承自IonicNativePlugin,这样就具备IonicNativePlugin的一些功能,最后导出SplashScreenOriginal的实例SplashScreen供我们调用

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
import { IonicNativePlugin, cordova } from '@ionic-native/core';
var SplashScreenOriginal = /** @class */ (function (_super) {
    __extends(SplashScreenOriginal, _super);
    function SplashScreenOriginal() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    SplashScreenOriginal.prototype.show = function () { return cordova(this, "show", { "sync": true }, arguments); };
    SplashScreenOriginal.prototype.hide = function () { return cordova(this, "hide", { "sync": true }, arguments); };
    SplashScreenOriginal.pluginName = "SplashScreen";
    SplashScreenOriginal.plugin = "cordova-plugin-splashscreen";
    SplashScreenOriginal.pluginRef = "navigator.splashscreen";
    SplashScreenOriginal.repo = "https://github.com/apache/cordova-plugin-splashscreen";
    SplashScreenOriginal.platforms = ["Amazon Fire OS", "Android", "iOS", "Windows"];
    return SplashScreenOriginal;
}(IonicNativePlugin));
var SplashScreen = new SplashScreenOriginal();
export { SplashScreen };

index.d.ts 规定了对象属性和方法

import { IonicNativePlugin } from '@ionic-native/core';
/**
 * @name Splash Screen
 * @description This plugin displays and hides a splash screen during application launch. The methods below allows showing and hiding the splashscreen after the app has loaded.
 * @usage
 * ```typescript
 * import { SplashScreen } from '@ionic-native/splash-screen/ngx';
 *
 * constructor(private splashScreen: SplashScreen) { }
 *
 * ...
 *
 * this.splashScreen.show();
 *
 * this.splashScreen.hide();
 * ```
 */
export declare class SplashScreenOriginal extends IonicNativePlugin {
    /**
     * Shows the splashscreen
     */
    show(): void;
    /**
     * Hides the splashscreen
     */
    hide(): void;
}

export declare const SplashScreen: SplashScreenOriginal;

上面的代码导出了一个能调用插件的通用实例,但是要在angular中使用,就得看看ngx的代码

ngx/index.js
分析:对index.js而言多了__decorate方法,在调用该方法后实例上多了__annotations__隐式属性,该属性让实例以Injectable的方式注入angular中得以调用,可以理解为加了个

@Injectable({
    providedIn: "root"
})

调用__decorate前

调用__decorate后

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Injectable } from '@angular/core';
import { IonicNativePlugin, cordova } from '@ionic-native/core';
var SplashScreen = /** @class */ (function (_super) {
    __extends(SplashScreen, _super);
    function SplashScreen() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    SplashScreen.prototype.show = function () { return cordova(this, "show", { "sync": true }, arguments); };
    SplashScreen.prototype.hide = function () { return cordova(this, "hide", { "sync": true }, arguments); };
    SplashScreen.pluginName = "SplashScreen";
    SplashScreen.plugin = "cordova-plugin-splashscreen";
    SplashScreen.pluginRef = "navigator.splashscreen";
    SplashScreen.repo = "https://github.com/apache/cordova-plugin-splashscreen";
    SplashScreen.platforms = ["Amazon Fire OS", "Android", "iOS", "Windows"];
    SplashScreen = __decorate([
        Injectable()
    ], SplashScreen);
    return SplashScreen;
}(IonicNativePlugin));
export { SplashScreen };

ngx/index.d.ts 类型规定和index.d.ts区别不大

import { IonicNativePlugin } from '@ionic-native/core';
/**
 * @name Splash Screen
 * @description This plugin displays and hides a splash screen during application launch. The methods below allows showing and hiding the splashscreen after the app has loaded.
 * @usage
 * ```typescript
 * import { SplashScreen } from '@ionic-native/splash-screen/ngx';
 *
 * constructor(private splashScreen: SplashScreen) { }
 *
 * ...
 *
 * this.splashScreen.show();
 *
 * this.splashScreen.hide();
 * ```
 */
export declare class SplashScreen extends IonicNativePlugin {
    /**
     * Shows the splashscreen
     */
    show(): void;
    /**
     * Hide the splashscreen
     */
    hide(): void;
}

package.json 我们需要用到的属性有author作者、dependencies依赖、description插件描述、license、module模块入口、name插件名称、peerDependencies同级依赖、repository插件仓库地址、typings类型规定、version插件版本号

{
  "_args": [
    [
      "@ionic-native/splash-screen@5.9.0",
      "/Users/linli/jobProjects/ionic-angular-demo"
    ]
  ],
  "_from": "@ionic-native/splash-screen@5.9.0",
  "_id": "@ionic-native/splash-screen@5.9.0",
  "_inBundle": false,
  "_integrity": "sha512-6IhAEtVBf8lE7HdLgs+GLm83z9ukfdSwbKS9oMciJe8dpXTY3J2B2Fy8HgXpo88phccHXny1acEpFndns3oEkA==",
  "_location": "/@ionic-native/splash-screen",
  "_phantomChildren": {},
  "_requested": {
    "type": "version",
    "registry": true,
    "raw": "@ionic-native/splash-screen@5.9.0",
    "name": "@ionic-native/splash-screen",
    "escapedName": "@ionic-native%2fsplash-screen",
    "scope": "@ionic-native",
    "rawSpec": "5.9.0",
    "saveSpec": null,
    "fetchSpec": "5.9.0"
  },
  "_requiredBy": [
    "/"
  ],
  "_resolved": "https://registry.npmjs.org/@ionic-native/splash-screen/-/splash-screen-5.9.0.tgz",
  "_spec": "5.9.0",
  "_where": "/Users/linli/jobProjects/ionic-angular-demo",
  "author": {
    "name": "ionic"
  },
  "bugs": {
    "url": "https://github.com/ionic-team/ionic-native/issues"
  },
  "dependencies": {
    "@types/cordova": "latest"
  },
  "description": "Ionic Native - Native plugins for ionic apps",
  "homepage": "https://github.com/ionic-team/ionic-native#readme",
  "license": "MIT",
  "module": "index.js",
  "name": "@ionic-native/splash-screen",
  "peerDependencies": {
    "rxjs": "^5.5.0 || ^6.5.0",
    "@ionic-native/core": "^5.1.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ionic-team/ionic-native.git"
  },
  "typings": "index.d.ts",
  "version": "5.9.0"
}

3.2 使用ths-cli脚手架生成ionic-native插件

分析完@ionic-native插件构造后我们现在就利用脚手架来生成上面插件的结构吧!
ths-cli是本人为公司写的脚手架->ths-cli<- 目前只有create-ionic-native命令,可以创建@ionic-native插件供angular项目使用,使用方法如下

// ths-cli已占用故用ths-cli2
npm install ths-cli2 -g

使用

ths-cli create-ionic-native ths-native-toast
Usage: ths-cli <command> [项目名称]

Options:
  -V, --version        output the version number
  -h, --help           display help for command

Commands:
  create-ionic-native  创建ionic-native插件
  help [command]       display help for command

create-ionic-native 表示创建ionic-native插件的指令

ths-native-toast 表示想要生成的插件名称

演示,冒号后面是手动输入的内容,其中cordova的id需要和上述cordova插件的名称一致

ths-cli create-ionic-native ths-native-toast
> 正在下载项目模板,源地址:git@github.com:qtpalmtop/templates-ionic-native.git#master
> 插件的名称 (ThsPlugin): ThsToast
> 插件的id (ths-native-plugin): ths-native-toast
> 插件的版本号 (1.0.0): 1.0.0
> 对应的cordova插件的id  (cordova-plugin-ths-pluginName): cordova-plugin-ths-toast
> 插件的简介 (A plugin named ThsPlugin): show toast
> 插件的git地址 (https://github.com/apache/cordova-plugin-ths-pluginName): https://github.com/qtpalmtop/cordova-plugin-ths-toast
正在初始化项目模板:ths-native-toast
✔ 创建成功:)


执行完后,当前目录下将会出现创建好的ths-native-toast插件


其中ngx/index.js,可以看出其结构已经与@ionic-native插件一模一样,完全可以当作@ionic-native插件使用

发布插件

cd ths-native-toast
npm publish

随后在angular项目中即可通过npm install ths-native-toast, 并在app.module.ts中导入插件

4 在angular项目中调用自定义插件

app.module.ts

import { ThsToast } from 'ths-native-toast/ngx';

@NgModule({
    ...,
    providers: [
        ThsToast
    ]
})

app.component.ts

import {ThsToast} from 'ths-native-toast/ngx';

    constructor(private thsToast: ThsToast) {

    }

    ngOnInit() {
        this.showToast()
    }

    /**
    * 弹出提示
    **/
    showToast(): void {
        this.thsToast.show('hello world', () => {
            console.log('call toast success');
        }, (error) => {
            console.log('call toast error');
        });
    }

app真机运行效果


控制台打印输出

call toast success

5 总结

参考文章:

基于node.js的脚手架工具开发经历

Cordova自定义插件开发

nodejs官方文档

如有疑问或指正,欢迎在评论区留言,相逢便是缘,如果觉得本文对你有所帮助,不妨点个赞鼓励下嘿嘿

03-05 20:27