- 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 icluded 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.
Declaration file (foo.d.ts):
declaremodule"foo"{functiona();classFoo{publicvar1: number;}}Implementation file (foo.ts):
importdefinition=require("foo");exportfunctiona(){// do somethign here}// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsdefinition.Foo{publicvar1: number;}The module implementation depends on native APIs ONLY and the common pure JavaScript code between platform-specific implementations is minimal.
Declaration file (foo.d.ts):
declaremodule"foo"{classFoo{publicrunning: number;publicstart(): void;publicstop(): void;}}Android implementation file (foo.android.ts):
importdefinition=require("foo");// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsdefinition.Foo{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):
importdefinition=require("foo");// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsdefinition.Foo{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:
Declaration file (foo.d.ts):
declaremodule"foo"{classFoo{publicrunning: number;publicstart(): void;publicstop(): void;}}Common implementation file (foo-common.ts):
importdefinition=require("foo");// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsdefinition.Foo{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):
importcommon=require("foo-common");// require the common file and extend the base common implementationexportclassFooextendscommon.Foo{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):
importcommon=require("foo-common");// require the common file and extend the base common implementationexportclassFooextendscommon.Foo{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:
Declaration file (foo.d.ts):
declaremodule"foo"{classFoo{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 usersdeclaremodule"foo-native"{functionstartNative();functionstopNative();}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):
importdefinition=require("foo");importfooNative=require("foo-native");// require the definition and put implements clause to ensure API consistency between the declaration and implementationexportclassFooimplementsdefinition.Foo{publicrunning: number;publicstart(): void{this.running=true;// do the native call through the FacadefooNative.startNative();}publicstop(): void{this.running=false;// do the native call through the FacadefooNative.stopNative();}}