Creating NativeScript Plugins in TypeScript

ns_plugins_header

In this article, I’ll give you the skinny on what you need to know to build cross-platform NativeScript plugins using TypeScript including posting them to GitHub and, finally, publishing them via npm. This article isn’t intended to teach you TypeScript or go over the specifics of building cross-platform plugins, but it will hopefully help you get set up to create your plugin and walk you through building and publishing it once you do.

Using TypeScript to write your NativeScript plugin raises some questions:

  1. What do I need?
  2. What’s a good workflow to develop using TypeScript?
  3. I see dozens of TypeScript warnings for {N} modules and for various native classes when I build my plugin. Is this a serious problem?
  4. Should I include the compiled .js files on GitHub?
  5. How do I ensure that when I publish to npm, the consumer will get the .js files and not just .ts files?

I’ll address each of these throughout this tutorial.

What do I need?

Assuming you already have npm installed (via Node.js), here’s all that you need:

npm install -g typescript

This will install the TypeScript compiler (accessible via tsc) that you will use to compile your source.

What’s a Good Workflow to Develop Using TypeScript?

Now that TypeScript is set up, you’re ready to develop. Let’s do this!

I’m not going to go over the details of the TypeScript language here but I’d recommend reading this if you want to learn more about that.

Your plugin will be made up of two platform files: yourplugin.android.ts and yourplugin.ios.ts. If you don’t have them yet, go ahead and create them. These files will contain the core logic of your plugin for each specific platform (I’m focusing here on the process of building and publishing the plugin rather than the specifics of a particular plugin – so feel free to get creative and build whatever you feel inspired to).

Now add a new file named tsconfig.json into the root with the following contents:

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "removeComments": true,
        "experimentalDecorators": true,
        "sourceMap": true
    },
    "files": [
      "yourplugin.android.ts", 
      "yourplugin.ios.ts"
    ],
    "compileOnSave": false
}

This configuration will tell tsc what to do and how to do what it needs to do. More details about the tsconfig.json options can be found here.

Before we go any further, let’s prepare a way test our TypeScript-based plugin by creating a NativeScript project. We will use a Hello World TypeScript template by using the new --template option available with the NativeScript 1.6 CLI to get us started quickly and easily:

tns create demo --template tns-template-hello-world-ts

Ok great. We now have the basics in place. Go ahead and run tsc in the root to try compiling your source. Go ahead. Try it.

TypeScript Warnings Anyone?

If you don’t have any warnings or errors, you’re either really good or really lucky.

You're Perfect!

You will very likely see TypeScript warnings, especially if you’re using platform specific native classes or importing NativeScript modules like these for example:

error TS2307: Cannot find module 'ui/content-view'.
error TS2307: Cannot find module 'ui/core/proxy'.
error TS2307: Cannot find module 'ui/core/dependency-observable'.

The errors will vary widely based on the specifics of your plugin. You’ll want to pay attention to the ones specific to the custom code you wrote and fix those. The good news is you can ignore the others.

Since your tsconfig.json does not contain any files pointing to definitions for those modules or classes, tsc outputs the various cannot find or does not exist errors. In most cases, it will still compile your plugin just fine, unless there were specific problems with your custom code.

To keep your warnings/errors to a minimum, you can at least clear the NativeScript module related ones. You can actually use the Telerik NativeScript (tns for short) core modules that were installed with the demo to help us out here.

Open up your tsconfig.json and add this to files:

{
    ...
    "files": [
      "demo/node_modules/tns-core-modules/tns-core-modules.d.ts",
      "yourplugin.android.ts", 
      "yourplugin.ios.ts"
    ],
    ...
}

Ok now trying running tsc again. You should see less TypeScript errors/warnings now (crossing fingers).

Should I Include the Compiled .js Files on GitHub?

First of all, hooray! We have JavaScript files now!

You should have our plugin’s .js files that consumers need to use your plugin now. However, you actually don’t want them on GitHub. They will just create merge conflicts when merging the pull requests from the hundreds of collaborators your plugin will (hopefully) have. So add this to your .gitignore file:

*.js
*.js.map
*.log
demo/app/*.js
demo/*.d.ts
demo/platforms
demo/node_modules
node_modules

Don’t worry! Your consumers will still be able to get those .js files. You are going to publish them. First though, let’s make sure your plugin is set up as a dependency in your demo:

cd demo
tns plugin add ..

You will need to further set things up in your demo to try it all out just as you would with any regular NativeScript project. Then you can of course start adding platforms and try things out in simulators or devices:

tns platform add android  // or tns platform add ios
tns run android   // or tns emulate ios

Cool!

Showtime. Let’s Publish.

First, let’s create a new file called .npmignore in the root directory with the following contents:

demo/
*.png
*.log

There are a handful of things to note about this:

  • This probably goes without saying, but when we publish our plugin, everything specified here will be ignored and not in the package when consumers install your plugin.
  • We are not ignoring .js files here.
  • You don’t want your demo coming down over the wire every time someone needs to use your plugin.
  • You may not have any screenshots yet, but you might in the future. You don’t want those .png‘s being installed with your plugin every time either.
  • Feel free to add anything else here that is not necessary for the usage of your plugin.

Now every time we make changes to our .ts source files, we will want to make sure that the latest changes are actually built out to .js files before we publish. Open your package.json and add a build script like this:

{
  ...
  "scripts": {
    "build": "tsc"
  },
  ...
}

Try it out like this:

npm run build

Again, you may see TypeScript errors/warnings. Hopefully they are the type you can ignore.

We would use a prepublish script here if it worked like you would think. Unfortunately it doesn’t at the moment (read more here). Hopefully this will be fixed soon. But for now, before you publish a new version, just be sure you run npm run build to build the latest!

Always make sure you bump the plugin version, then:

npm publish

Enjoy your TypeScript development and let’s see some NativeScript plugins!

Please Note: You can get a Quick Start on all of this and even more by using the nativescript-plugin-seed here.

Header image courtesy of Samuel M. Livingston

NativeScript

Comments

  • Awesome article Nathan!

    I’ll add one thing to this list that has helped me out—the NativeScript/NativeScript repo on GitHub has .d.ts files that include the entirety of the APIs available on iOS and Android, which I’ve found invaluable since I have very little native iOS or Android experience. Here’s the Android one: https://raw.githubusercontent.com/NativeScript/NativeScript/master/android17.d.ts. Here’s the iOS one: https://raw.githubusercontent.com/NativeScript/NativeScript/master/ios.d.ts.

    I’m curious if you’ve used these files at all, or if you have any tips that have helped you hit native APIs?

    • Nathan Walker

      Thank you TJ, great feedback! Yes I want to include that subject in the article. At the moment, I’m unsure of the best way forward on that given the cross platform plugin files. Right now, if you add the `declaration: true` option in `tsconfig.json`, it will generate sometimes an identical .d.ts file for each platform file. This can be seen here: https://github.com/NathanWalker/nativescript-plugin-seed if you add the `declaration: true` option and then run `npm run build`… notice the `.d.ts` files generated for both `yourplugin.ios.d.ts` and `yourplugin.android.d.ts` definitions are identical. I need to ask a core team member about this to determine how best to handle this with plugins.

  • Just wanted to mention that the plugin seed Nathan references in this article, https://github.com/NathanWalker/nativescript-plugin-seed, is quite excellent. If you want to build a NativeScript plugin in TypeScript—and who wouldn’t want to 😀—it really is the best place to get started.

    git clone https://github.com/NathanWalker/nativescript-plugin-seed.git 🤘

  • Pingback: Dew Drop – March 7, 2016 (#2202) | Morning Dew()

  • burkeholland

    I copied the nativescript-geolocation template and ended up with a grunt file that moves and packages the plugin. I think this is more straightforward. I’ll be using this one for next time.