- Each module resides in a separate folder.
- There must be a package.json file in this folder which tells the NS Runtime which is the main file of the module to load.
- There is a declaration (*.d.ts) file describing the public API of the module.
- When there is a *.android.ts named file this tells our CLI that this file is Android-specific and should be included in Android builds ONLY. When a build is started for the Android platform, the .android part of the file is stripped in the application package. For example foo.android.js will become *foo.js. Same convention works for *.ios.ts files.
There are several major scenarios when writing modules:
The module implementation contains pure JavaScript code ONLY and does not depend on native APIs. In this case the entire logic is executed on the JS Virtual Machine side and the TNS Runtime is not involved.
Example:matrix module
Declaration file (foo.d.ts):
exportfunctiona();exportclassFoo{publicvar1: number;}Implementation file (foo.ts):
import{FooasFooDefinition}from".";exportfunctiona(){// do something here}// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsFooDefinition{publicvar1: number;}The module implementation depends on native APIs ONLY and the common pure JavaScript code between platform-specific implementations is minimal.
Example:timer module
Declaration file (foo.d.ts):
exportclassFoo{publicrunning: number;publicstart(): void;publicstop(): void;}Android implementation file (foo.android.ts):
import{FooasFooDefinition}from".";// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsFooDefinition{publicrunning: number;publicstart(): void{// Call android APIs - e.g. android.os.SystemClock.[xxx]this.running=true;}publicstop(): void{// Call android APIs - e.g. android.os.SystemClock.[xxx]this.running=false;}}iOS implementation file (foo.ios.ts):
import{FooasFooDefinition}from".";// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsFooDefinition{publicrunning: number;publicstart(): void{// Call iOS APIs - e.g. Foundation.NSObject.[xxx]this.running=true;}publicstop(): void{// Call iOS APIs - e.g. Foundation.NSObject.[xxx]this.running=false;}}The module is more complex and contains significant part of pure JavaScript code as well as native APIs calls.
In this case we will need to reuse the common JavaScript code and to split the implementation only for the platform specific native APIs. There are two different approaches here:
Separate the common implementation (code) in a base class. Add two specific files that inherit the base class and provide the platform-specific implementation
This is the way most of the UI modules are written.
Example:image module
Declaration file (foo.d.ts):
exportclassFoo{publicrunning: number;publicstart(): void;publicstop(): void;}Common implementation file (foo-common.ts):
import{FooBaseasFooDefinition}from".";// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooBaseimplementsFooDefinition{publicrunning: number;publicstart(): void{this.running=true;// add some common implementation here}publicstop(): void{this.running=false;// add some common implementation here}}Android implementation file (foo.android.ts):
import{FooBase}from"./foo-common";// require the common file and extend the base common implementationexportclassFooextendsFooBase{publicstart(): void{// call the base method which does the common jobsuper.start();// add platform-specific implementation - e.g. call android.os.SystemClock.[xxx]}publicstop(): void{// call the base method which does the common jobsuper.stop();// add platform-specific implementation - e.g. call android.os.SystemClock.[xxx]}}iOS implementation file (foo.ios.ts):
import{FooBase}from"./foo-common";// require the common file and extend the base common implementationexportclassFooextendsFooBase{publicstart(): void{// call the base method which does the common jobsuper.start();// add platform-specific implementation - e.g. call Foundation.NSObject.[xxx]}publicstop(): void{// call the base method which does the common jobsuper.stop();// add platform-specific implementation - e.g. call Foundation.NSObject.[xxx]}}Extract the platform specific implementation in a separate Facade and aggregate/use it within the JavaScript implementation
Example:file-system module (Note:file-system-access is the platform specific implementation)
Declaration file (foo.d.ts):
exportclassFoo{publicrunning: number;publicstart(): void;publicstop(): void;}Native Implementation Declaration file (foo-native.d.ts):
//@private// The above statement marks this definition as private so that it is not visible to the usersexportfunctionstartNative();exportfunctionstopNative();Android Native Implementation file (foo-native.android.ts):
exportfunctionstartNative(){// call native code here}exportfunctionstopNative(){// call native code here}iOS Native Implementation file (foo-native.ios.ts):
exportfunctionstartNative(){// call native code here}exportfunctionstopNative(){// call native code here}Common implementation file (foo.ts):
import{FooBaseasFooDefinition}from".";import{startNative,stopNative}from"./foo-native";// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsFooDefinition{publicrunning: number;publicstart(): void{this.running=true;// do the native call through the FacadestartNative();}publicstop(): void{this.running=false;// do the native call through the FacadestopNative();}}