📺 "One project. One language. Three apps." Learn more in our upcoming code sharing webinar!

NativeScript Core

Ensure Plugins Quality

NativeScript plugins are the main building blocks for NativeScript applications. These building blocks should work properly installed into applications:

  • built for Android
  • built for iOS
  • bundled with webpack

Ignoring any of these non-functional requirements could lead to an app that doesn’t work as expected. Throughout this article we'll be referring to the verification of those requirements as 'sanity checks' without writing a single line of test.

Prerequisites

NativeScript apps can be written in both Angular, or just plain JavaScript/TypeScript and they can also take advantage of the webpack bundling to cut off the excess code. In order to ensure that your plugin runs reliably in any NativeScript application, there are certain prerequisites you may need to complete.

All plugins should have a demo folder that contains a demo application showing how the plugin works. If you use the NativeScript plugin seed you will have this folder by default. If your plugin is a user interface plugin, and you need to test the plugin in both Angular and non-Angular apps, you should have an additional demo-angular folder containing an Angular app you can test your plugin in. Refer to the article "Supporting Angular in UI Plugins" for more details.

my-plugin
├── demo
├── demo-angular
└── src
     └── package.json

NOTE: It is very handy to have the plugin and demo application(s) in the same repository. The NativeScript official plugin seed defines this structure so if the plugin is based on it, the plugin’s source and the demo app are nicely organized.

In order to ease the process add the following scripts in your package.json file under src folder (the plugins source folder).

"ngc": "node --max-old-space-size=8192 ./node_modules/.bin/ngc"

This script will initiate Ahead of Time (AOT) compilation. The parameter max-old-space-size is a workaround to fix heap out of memory errors when running node binaries. It's a common issue when using TypeScript 2.1+ and the Angular compiler (ngc). Check out this issue for more information - https://github.com/angular/angular-cli/issues/5618.

NOTE The script above is needed only if the plugin implements some specific Angular wrappers for Angular support.

"build": "npm i && tsc && npm run ngc"

This script will install all NativeScript plugin’s dependencies, compile TypeScript files and initiate Ahead of Time (AOT) compilation.

NOTE The command npm run ngc is needed only if the plugin implements some specific Angular wrappers for Angular support. If this is not the case, it can be removed.

"prepublishOnly": "npm run build"

In the package.json under your demo and demo-angular folders add the following script:

"build.plugin": "cd ../src && npm run build"

This script will be executed before the package is prepared and packed, only on npm publish. More details can be found in the npm-script documentation. This approach is important because it ensures the plugin’s TypeScript is compiled and the plugin’s required metadata is generated every time before publishing.

Refer to the package.json file of the nativescript-facebook plugin where these scripts are also added.

Checking for Readability, Maintainability, and Functionality Errors

TSLint is a great tool for static analysis of your plugin’s code. It will test the plugin for readability and maintainability as well as functionality errors based on customizable rules. A complete list with the available TSLint rules can be found in the tslint repository.

The official NativeScript plugin seed recommends TSLint rules defined in this tslint.json file.

my-plugin
├── demo
├── demo-angular
├── src
|    └── package.json
└── tslint.json

TSLint could be easily incorporated into any NativeScript plugin by following these steps:

  1. Add tslint.json file on root level.
  2. Add the following script in your plugin’s src/package.json file.
"ci.tslint": "npm i && tslint '**/*.ts' --config '../tslint.json' --exclude '**/node_modules/**'"

This script executes the tslint command passing the tslint rules defined in tslint.json file. The installed node_modules will be excluded from the static analysis. Having tslint.json on root level allows using the same TSLint rules for both demo apps by adding the same script.

Now the command npm run ci.tslint will start a static analysis.

Checking in Bundled NativeScript Applications

Key benefits of using webpack to bundle a NativeScript app are:

  • improve the app startup time
  • decrease the size of the app

NOTE: You can read more details about the benefits of bundling NativeScript apps in the “Using Webpack to Bundle Your Code” article.

NativeScript plugins should work seamlessly in bundled app. This could be verified by testing the bundled demo apps. In order to enable the app for bundling, there is some configuration required. The entire setup is defined in “Using Webpack to Bundle Your Code”.

Once the nativescript-dev-webpack plugin is installed in the demo app(s), several scripts will be added into apps' package.json files.

  "scripts": {
    "ns-bundle": "ns-bundle",
    "publish-ios-bundle": "npm run ns-bundle --ios --publish-app",
    "start-android-bundle": "npm run ns-bundle --android --run-app",
    "start-ios-bundle": "npm run ns-bundle --ios --run-app",
    "build-android-bundle": "npm run ns-bundle --android --build-app",
    "build-ios-bundle": "npm run ns-bundle --ios --build-app"
  }

To minify your JavaScript files, it is handy to use --uglify flag:

"build-ios-bundle": "npm run ns-bundle --ios --build-app --uglify"

You have to assure that your plugin doesn't break successful bundling of the application with --uglify flag.

Generation of V8 heap snapshots is another optimization that you have to assure is supported by your plugin. When you build your application using the default configuration from the nativescript-dev-webpack plugin, two assets are generated - bundle.js and vendor.js. The first one contains the application code and the second one - every node package that is required in the ./app/vendor.ts|js file. You need to add your plugin to the list of required ones in ./app/vendor.ts|js:

// app/vendor.ts|js

require("./vendor-platform");
// ...

require("my-awesome-plugin");

You can enable the generation of V8 snapshots by providing the --snapshot:

"build-android-bundle": "npm run ns-bundle --android --build-app --uglify --snapshot"

Now the command npm run build-android-bundle will bundle the minified and 'snapshotted' NativeScript application and build it for Android. The result is an optimized Android application that uses the plugin.

Refer to the nativescript-facebook demo app which is configured and webpack ready. Notice the package.json, vendor.ts, vendor-platform.ts (vendor-platform.android.ts and vendor-platform.ios.ts) and bundle-config.ts

That same sample’s demo-angular app shows how to set up and test webpack in NativeScript apps that use Angular.

Checking in Application Built for Android and iOS

Perhaps the most important sanity checks is whether the demo application consuming the plugin can actually be built. NativeScript supports Android and iOS so both platforms should be covered. The recommendation is to build with latest SDK for Android and iOS.

The NativeScript command for building Android and iOS apps is:

tns build android and tns build ios

Read more details regarding building project with NativeScript CLI.

Automate All Checks with Travis CI

Travis CI is a great way to automate plugin’s sanity checks. It is free for open-source projects. More details can be found in Travis CI documentation. Travis CI will boot a virtual machine and execute commands based on the provided configuration in your .travis.yml file.

First things first! Add an empty .travis.yml file on the root level of your plugin.

my-plugin
├── demo
├── demo-angular
├── src
|    └── package.json
├── tslint.json
└── .travis.yml

NOTE: If you use the NativeScript plugin seed, you have an initial .travis.yml file setup.

This sample uses Build Matrix to initiate several runs as a result of one and Build Stages to separate the execution into stages. The flow will be as follows: 1. Test for Readability, Maintainability and Functionality Errors 2. WebPack and Build Demo Apps with Your Plugin Installed 3. Build Demo Apps with Your Plugin Installed

Each step starts after successful completion of the previous one. In this way, if there is a functional error, for example, the entire run will be terminated after the fall of the first step and the rest of the steps will not be executed. This behavior is controlled by Build Stages.

According to the Build Lifecycle of each Travis CI build, install is the right phase to install any required dependencies.

Add following commands in the install phase in .travis.yml file:

Install nativescript as a global node module.

- npm install -g nativescript

Configures anonymous usage reporting for the NativeScript CLI. Read more about CLI usage reporting.

- tns usage-reporting disable

Configures anonymous error reporting for the NativeScript CLI. Read more about CLI error reporting.

- tns error-reporting disable

As a result the install phase should be:

install:
    - npm install -g nativescript
    - tns usage-reporting disable
    - tns error-reporting disable

Refer to nativescript-facebook .travis.yml file to see this in reality.

As we mentioned earlier, the plugin should be sanity checked on Android as well as on iOS. The Android specific requirements can be defined in .travis.yml file in android section:

android:
  components:
    - tools
    - platform-tools
    - build-tools-26.0.1
    - android-26
    - android-23
    - extra-android-m2repository

tools and platform-tools components define that the latest revision of Android SDK Tools will be installed. Read more about Travis CI Environment for Android Project.

build-tools-26.0.1 component defines the BuildTools version that will be used.

android-26 component defines the SDK version used to compile the project.

extra-android-m2repository component defines the support library repositories.

Let's add the required stages using the Build Matrix.

Add the following snippet at the beginning of .travis.yml file:

matrix:
  include:

Then add the required stages:

1. Test for Readability, Maintainability and Functionality Errors

- stage: "Lint"
  language: node_js
  os: linux
  node_js: "6"
  script: cd src && npm run ci.tslint && cd ../demo && npm run ci.tslint && cd ../demo-angular && npm run ci.tslint

The machine that is going to be provisioned will be Linux with nodejs v6 installed on it as well as Oracle JDK v8. Finally the ci.tslint script will be executed for the plugin's code and for the demo apps.

2. WebPack and Build Demo Apps with Your Plugin Installed

- stage: "WebPack"
    os: osx
    env:
      - WebpackiOS="11"
    osx_image: xcode9.1
    language: node_js 
    node_js: "6"
    jdk: oraclejdk8
    script: cd demo && npm run build.plugin && npm i && npm run build-ios-bundle && cd ../demo-angular && npm run build.plugin && npm i && npm run build-ios-bundle
  - language: android
    os: linux
    env:
      - WebpackAndroid="26"
    jdk: oraclejdk8
    before_install: nvm install 6.10.3
    script: cd demo && npm run build.plugin && npm i && npm run build-android-bundle && cd ../demo-angular && npm run build.plugin && npm i && npm run build-android-bundle

This stage includes two builds that run in parallel — one for Android, and one for iOS. Note that the nodejs on the Linux machine is installed because it is not included in the image. The environment variables defined under env are used for informational purposes. These variables are not used anywhere in the code. The scripts that are executed build the plugin (take a look at package.json file of the demo apps) and then bundle and build each of the demo apps.

3. Build Demo Apps with Your Plugin Installed

- stage: "Build"
  env: 
    - BuildAndroid="26"
  language: android
  os: linux
  jdk: oraclejdk8
  before_install: nvm install 6.10.3
  script: cd demo && npm run ci.android.build && cd ../demo-angular && npm run ci.android.build
- os: osx
  env: 
    - BuildiOS="11"
    - Xcode="9.1"
  osx_image: xcode9.1
  language: node_js 
  node_js: "6"
  jdk: oraclejdk8
  script: cd demo && npm run ci.ios.build && cd ../demo-angular && npm run ci.ios.build

The scripts (ci.android.build and ci.ios.build) that are executed to build for iOS and Android are located in package.json file of any of the demo apps.

If everything is configured properly, the sanity checks will execute on every code change. The result, and whether the checks pass or not, will look like this:

The main benefit of having sanity checks in place for your NativeScript plugins is that you can develop without spending additional time to ensure your changes don't break existing applications depending on your plugin.

Do not forget to add a Travis CI badge in your NativeScript plugin's project! It reports live status of your CI build and makes your plugin look more reliable.

See Also