Building Your Own NativeScript Modules for npm

Since releasing NativeScript to the public two weeks ago, we’ve received a flood of feedback and questions, but the most common one we get is “how do I do {{ thing }} in NativeScript?“, where {{ thing }} is one of the thousands of functions that you might want to include in a native mobile app — maps, QR-code readers, bluetooth, Apple Pay, and so forth.

The great thing about NativeScript is that you can absolutely do all of those things in a NativeScript app, as the NativeScript runtime makes all Android and iOS APIs directly available. The tricky part is accomplishing many of those {{ thing }}s requires that you to write platform-specific code.

For example, let’s say you want to use a native map in your new app. To do that, you’d ideally write code like new Map(), or even better <Map>, but in today’s NativeScript you have to write code like MKMapView.alloc() for iOS and com.google.gms.maps.MapFragment for Android.

But don’t worry; there’s good news: NativeScript has a really slick mechanism for abstracting away this platform-specific code called NativeScript modules. Let’s look at what NativeScript modules are, how they help prevent you from writing platform-specific code, how you can help the NativeScript community out by writing your own, and how to distribute those modules via npm.

What are NativeScript Modules?

NativeScript modules are JavaScript modules that implement functionality for one to many mobile platforms. You can think of NativeScript modules as similar to Cordova plugins, as both abstract away platform-specific iOS, Android, and Windows code behind JavaScript APIs. However, there are some important differences between how NativeScript modules and Cordova plugins work, most notably in how you access native APIs.

Specifically, in Cordova plugins you write native code (Objective-C, Java, C#, etc) to access native APIs, whereas in NativeScript you access native APIs in JavaScript directly. To get an idea of what that looks like, let’s look at a simple NativeScript module.

The following code defines a NativeScript module named “device” that provides access to the device’s OS version (so “5.0”, “4.4.4”, “4.4.3”, etc for Android, and “8.2”, “8.1”, “8.0”, etc for iOS). There are three files:

  • device.ios.js has the module’s iOS implementation;
  • device.android.js has the module’s Android implementation;
  • some-other-file.js shows how to use the device module.
// device.ios.js
// Defines the device module's implementation for iOS
module.exports = {
    version: UIDevice.currentDevice().systemVersion
};

// device.android.js
// Defines the device module's implementation for Android
module.exports = {
    version: android.os.Build.VERSION.RELEASE
};

// some-other-file.js
// Code that uses the "device" module
var device = require( "./device" );
console.log( device.version );

And that’s it. Unlike Cordova you don’t need to write any configuration files to use NativeScript modules — you define the module, require() it, and use it. Let’s look at what’s exactly what’s happening here in greater detail, as there are several cool things worth mentioning.

Cool Thing #1: The .ios.* and .android.* naming convention

NativeScript has a built-in mechanism to choose the correct platform-specific implementation of a module at compile time. Meaning, given the example above, NativeScript automatically includes the device.ios.js in native iOS projects, and device.android.js in native Android projects.

This convention provides an elegant means of forking your code, as the code that uses your modules doesn’t have to care about which platform it’s running on. Notice how there’s no platform-specific code in some-other-file.js; the file just require()s the device module and uses it:

// some-other-file.js
// You don't need to include the platform-specific suffixes (i.e. "device.android" or "device.ios")
// as NativeScript figures that out for you automatically.
var device = require( "./device" );
console.log( device.version );

Cool Thing #2: The NativeScript runtime

The NativeScript runtime makes accessing native iOS and Android APIs beautifully succinct. Specifically, the NativeScript runtime is what makes the android.os.Build and UIDevice.currentDevice() APIs available to JavaScript. In fact, you can easily reference the iOS UIDevice and Android Build.VERSION docs this sample code uses.

For more information on how it works check out my article on How NativeScript Works.

Cool thing #3: CommonJS

NativeScript modules follow the same CommonJS spec that Node modules do. Meaning, if you already know how Node modules work, or even if you just have a rough idea how to use Grunt and Gulp, then you already mostly know what you need to use NativeScript modules.

If you don’t know how Node modules work, all you really need to know is how two keywords work: require and exports. For example, let’s refer back to the code from above:

// device.ios.js
module.exports = {
    version: UIDevice.currentDevice().systemVersion
};

// device.android.js
module.exports = {
    version: android.os.Build.VERSION.RELEASE
};

// some-other-file.js
var device = require( "./device" );
console.log( device.version );

In some-other-file.js the require() call imports the device module so that it can use it. The return value of require(), or the object that gets stored in the device variable, is determined by the use of the exports keyword in device.ios.js and device.android.js. In this case, because the device.\*.js files return an object with a version property, that object is returned and stored in some-other-file.js‘s device variable.

For a more detailed explanation of how CommonJS modules and the exports keyword work, check out this article.

Cool Thing #4: NativeScript’s core modules are all written using these conventions

The core of the NativeScript framework is nothing more than a series of NativeScript modules that follow the conventions discussed in this article. This means that if you want examples of how to write your own modules (and we’ll do that momentarily), you can peruse the NativeScript framework itself.

NativeScript’s core modules are stored in the nativescript/cross-platform-modules repo on GitHub. The modules are open source. You can file issues, and you can even contribute! In NativeScript projects these modules are stored in your tns_modules directory.

One thing to be aware of: the NativeScript core cross-platform modules are written in TypeScript. We use TypeScript because we find it speeds up our development processes, but that doesn’t mean you have to. You can write your own modules in TypeScript, as it’s a first-class citizen in NativeScript, or you can write them in JavaScript. If you do use TypeScript, you can get code completion for native APIs using our TypeScript declaration files. Here’s the iOS file and here’s the Android one.

With this in mind, the first thing to do when trying to do {{ thing }} in NativeScript is to look to see if there’s a cross-platform module that already does it. If so, great! If not, you file an issue to request one, or, if you’re looking for a super fun weekend project, you can build that module yourself. Let’s look at how.

Writing and Distributing NativeScript Modules on npm

Writing a NativeScript module is as simple as writing a CommonJS module that uses the NativeScript runtime to call native APIs. Having a bit of Objective-C and/or Java knowledge helps, but it’s certainly not a requirement.

I actually find that writing a NativeScript module provides an excellent introduction to native iOS and Android apps. I usually Google “What’s the API for {{ thing }} in [ Android | iOS ]” as a starting point. Having some npm experience also helps, but, even if you’ve never used npm, you’ll find registering a module to be a relatively easy and well documented process.

You can look at our docs for specifics of how the runtimes convert Objective-C/Java code to JavaScript (here’s Android’s; here’s iOS’s), but I find it’s easiest to look over a few existing NativeScript modules in the cross-platform-modules repo to get an idea of how things work.

Here are a few other best practices for writing NativeScript modules.

Best Practice #1: Start by copying an existing module

I recently created the NativeScript flashlight module, and I’ll be using that as the basis for the rest of this article. The module is fairly simple so I’d encourage you to copy the entire implementation and use it as a basis of your own. The module’s repo contains a fully functional demo of it in use (see the demo folder), which can be handy to refer to if you’re wondering how the pieces fit together.

Best Practice #2: Put shared code in a file named moduleName-common.js

Depending on the module you’re writing, you may have some code you want to share across platforms or you may not. If do want to share code, the NativeScript core modules use a convention of placing shared code in a moduleName-common.js file, and importing and extending that common code in the moduleName.android.js and moduleName.ios.js files.

To give you an idea of how this sharing works, take a look at the source of flashlight-common.js below:

// flashlight-common.js
var flashlight = {
    _on: false,
    toggle: function() {
        if ( flashlight._on ) {
            flashlight.off();
        } else {
            flashlight.on();
        }
        flashlight._on = !flashlight._on;
    },
    isOn: function() {
        return flashlight._on;
    }
};

module.exports = flashlight;

The common module implements two methods: toggle() and isOn(). There’s nothing about these two methods that’s specific to iOS or Android, so it makes sense to implement them in a common file to avoid duplicating code.

The methods that are platform-specific are on() and off(), and the two platform-specific files each contain an implementation shown below. Note how each module starts by requiring the common flashlight module, and then adds on the platform-specific implementations.

// flashlight.android.js
var flashlight = require( "./flashlight-common" );
var camera = android.hardware.Camera.open();
var p = camera.getParameters();

flashlight.on = function() {
    p.setFlashMode( android.hardware.Camera.Parameters.FLASH_MODE_TORCH );
    camera.setParameters( p );
    camera.startPreview();
};
flashlight.off = function() {
    p.setFlashMode( android.hardware.Camera.Parameters.FLASH_MODE_OFF );
    camera.setParameters( p );
    camera.stopPreview();
};

module.exports = flashlight;
// flashlight.ios.js
var flashlight = require( "./flashlight-common" );
var device = AVCaptureDevice.defaultDeviceWithMediaType( AVMediaTypeVideo );

flashlight.on = function() {
    device.lockForConfiguration( null );
    device.torchMode = AVCaptureTorchMode.AVCaptureTorchModeOn;
    device.flashMode = AVCaptureFlashMode.AVCaptureFlashModeOn;
    device.unlockForConfiguration();
};
flashlight.off = function() {
    device.lockForConfiguration( null );
    device.torchMode = AVCaptureTorchMode.AVCaptureTorchModeOff;
    device.flashMode = AVCaptureFlashMode.AVCaptureFlashModeOff;
    device.unlockForConfiguration();
};

module.exports = flashlight;

Best Practice #3: Name the repository nativescript-module-name

This is a Node convention that a lot of Node ecosystems (such as Grunt and Gulp) use to help users recognize modules that work in that ecosystem at a glance. Note that the NativeScript flashlight module is named “nativescript-flashlight”.

Best Practice #4: Register your module on npm

Because NativeScript modules are already Node modules they make perfect sense to include in npm. Registering your module in npm allows people to use npm install to include the modules in their NativeScript apps, and it also helps people discover that your module exists.

There are already a number of great articles on publishing npm modules (here’s my personal favorite), so I’m not going to reinvent the wheel here, but I will offer a few suggestions that are specific to NativeScript modules:

  • Make sure the “name” in your package.json uses the same “nativescript-module-name” convention discussed earlier.
  • Include “NativeScript” in your package.json under “keywords”. It’ll help us find your modules.

Tip: With a bit of extra configuration you can also allow usage of your module in XML. For some modules this is irrelevant (such as flashlight), but for some it’s incredibly useful — for example imagine using a map module in XML with <Map latitude="30" longitude="30" />. All XML elements you use in NativeScript apps — <Page>, <StackLayout>, <Button>, and so on — are all implemented as NativeScript modules using these exact same conventions.

Best Practice #5: Tell us about it!

We love hearing about new NativeScript modules and we’re happy to spread the word about new modules. Send us a tweet at @nativescript.

If you have any other questions about NativeScript modules, or you run into trouble building your own, feel free to reach out to us on Twitter or to comment on this article.

Can’t get enough NativeScript? Clark Sell, Sebastian Witalec, and I are running a six-hour workshop on NativeScript at TelerikNEXT this May. Come meet us and hack on NativeScript.

Comments

  • Pingback: Dew Drop – March 23, 2015 (#1979) | Morning Dew()

  • Oscar

    TJ, quote: “With a bit of extra configuration you can also allow usage of your module in XML”. It should be very interesting and helpful if your next article shows us precisely that. 😉

    • Indeed. I’ve gotten a number of questions about this so I’ll see what I can do 🙂

      • Oscar

        That’s really great, TJ!

        And maybe just to nudge you 😉 or anyone in the NativeScript team a bit further, there’s another very important consideration we’re factoring in visa-a-vis NativeScript replacing PhoneGap/Apache Cordova in our toolset.

        With PhoneGap, we are able to reuse our HTML5/Javascript code base for mobile-web, mobile-hybrid and DESKTOP with practically no architectural issues to overcome. But because of NativeScript’s native pedigree, there are architectural or application design issues we need to resolve to effect maximum reuse of our code assets.

        I hope you and the NativeScript team will be able to find time to craft some sort of a BEST PRACTICES article, tutorial or white paper on how to architect or design an application that targets ALL of native (NativeScript), mobile-web (browser) and desktop (Chrome packaged, node-webkit or Adobe AIR apps) environments or platforms for MAXIMUM CODE REUSE.

        Such a guide will help take away a lot of the hesitation we currently have about using NativeScript in place of PhoneGap/Apache Cordova.

        Is this something you think you and your team can do? If so, when?

        • Yep, this is definitely a common thing that comes up. The best advice I have is the good ol’ software development practice of dividing your code into small modular units with as few dependencies as possible. If you isolate your environment-specific code in certain files it makes your environment-agnostic code (e.g. business logic, model objects, etc) easier to share.

          We get a lot of questions about people wanting to port code to NativeScript, so I think this is something we’ll cover in detail at some point. I don’t want to give any specific dates because my list is already pretty long 🙂

          • Oscar

            Thanks TJ. I hope this cross-platform development best practices tome from you or your team gets done soon enough.

            We, of course, can always do “modular”, but a cross-platform development best practices tome from the NativeScript Team will be ideal, as we consider your Team as the “source of truth” on anything that has to do with NativeScript.

            This also lowers the “barriers to entry” from Apache Cordova/PhoneGap and the Web and desktop development community to NativeScript.

          • Oscar

            Thanks TJ. I hope this cross-platform development best practices tome from you or your team gets done soon enough.

            We, of course, can always do “modular”, but a cross-platform development best practices tome from the NativeScript Team will be ideal, as we consider your Team as the “source of truth” on anything that has to do with NativeScript.

        • Yep, this is definitely a common thing that comes up. The best advice I have is the good ol’ software development practice of dividing your code into small modular units with as few dependencies as possible. If you isolate your environment-specific code in certain files it makes your environment-agnostic code (e.g. business logic, model objects, etc) easier to share.

          We get a lot of questions about people wanting to port code to NativeScript, so I think this is something we’ll cover in detail at some point. I don’t want to give any specific dates because my list is already pretty long 🙂

      • Oscar

        That’s really great, TJ!

        And maybe just to nudge you 😉 or anyone in the NativeScript team a bit further, there’s another very important consideration we’re factoring in visa-a-vis NativeScript replacing PhoneGap/Apache Cordova in our toolset.

        With PhoneGap, we are able to reuse our HTML5/Javascript code base for mobile-web, mobile-hybrid and DESKTOP with practically no architectural issues to overcome. But because of NativeScript’s native pedigree, there are architectural or application design issues we need to resolve to effect maximum reuse of our code assets.

        I hope you and the NativeScript team will be able to find time to craft some sort of a BEST PRACTICES article, tutorial or white paper on how to architect or design an application that targets ALL of native (NativeScript), mobile-web (browser) and desktop (Chrome packaged, node-webkit or Adobe AIR apps) environments or platforms for MAXIMUM CODE REUSE.

        Such a guide will help take away a lot of the hesitation we currently have about using NativeScript in place of PhoneGap/Apache Cordova.

        Is this something you think you and your team can do? If so, when?

    • Indeed. I’ve gotten a number of questions about this so I’ll see what I can do 🙂

  • Oscar

    TJ, quote: “With a bit of extra configuration you can also allow usage of your module in XML”. It should be very interesting and helpful if your next article shows us precisely that. 😉

  • x1

    Hello, please tell me how to connect node_modules and use in your project

    For example I need to connect orm

  • x1

    Hello, please tell me how to connect node_modules and use in your project

    For example I need to connect orm

  • Hello TJ,
    Currently we are using Appcelerator’s Titanium/Alloy platform for delivering mobile applications. I am trying to assess how difficult it would be to migrate. To start, if we were to migrate our models from the Alloy’s version of backbone to use the latest Backbone.js from NPM, how difficult would that be?

    • If your model objects are being used only as models—aka they’re completely abstracted from the view—then you should be able to port them pretty easily. Your view logic will definitely have to change quite a bit though. You will have to convert your view logic to use NativeScript’s data binding syntax, and you’ll have to use NativeScript’s UI components.

      I personally haven’t tried to use Backbone in a NativeScript app, but I see no reason why it shouldn’t work. I’d encourage you to build a small proof-of-concept app to see how it goes.

    • If your model objects are being used only as models—aka they’re completely abstracted from the view—then you should be able to port them pretty easily. Your view logic will definitely have to change quite a bit though. You will have to convert your view logic to use NativeScript’s data binding syntax, and you’ll have to use NativeScript’s UI components.

      I personally haven’t tried to use Backbone in a NativeScript app, but I see no reason why it shouldn’t work. I’d encourage you to build a small proof-of-concept app to see how it goes.

  • Hello TJ,
    Currently we are using Appcelerator’s Titanium/Alloy platform for delivering mobile applications. I am trying to assess how difficult it would be to migrate. To start, if we were to migrate our models from the Alloy’s version of backbone to use the latest Backbone.js from NPM, how difficult would that be?

  • DavyDev

    I try to install Nativescript but when I am on the last step (7) to execute
    and type : c:npm i -g nativescript de command processor cannot find this. heeelp

  • Digislayer

    Hello, I need to develop a module which requires Network access. Unfortunately, Android doesn’t like network activity on main thread, and I’m forced to wrap my logic inside a Java thread. Now, the problem is: How do I capture the response from my Java thread and use it to execute a JavaScript callback?

    Also, can we write Java based anonymous inner type classes(ex: Runnables) in NativeScript? I seem to have trouble doing so.

  • Pingback: Using external modules in NativeScript UI declarations - DexPage()

  • Pingback: Nativescript Mobile Applications - Promising Future of Technology()