As a NativeScript plugin author, there are 6 main points to understand about updating your plugin for NativeScript 7:
dependencies
updatestns-core-modules
and flatten all imports to come from just @nativescript/core
tns-platform-declarations
and replace with @nativescript/types
We will be introducing a new plugin seed soon with an improved architecture (over the current plugin seed), build tooling and setup to help you maintain all your plugins easier into the future. It will also allow easy drop in's of your existing plugins to use it (We will be dropping a blog post on this in next 2 weeks upon it's availability).
Let's address each of these points respectively.
"devDependencies": {
"@nativescript/core": "~7.0.0",
"@nativescript/types": "~7.0.0",
"@nativescript/webpack": "~3.0.0",
"typescript": "~3.9.0",
Update your tsconfig.json. This highlight's the key parts so only showing the most important.
BEFORE:
"compilerOptions": {
"target": "es5",
"module": "commonjs",
AFTER:
"compilerOptions": {
"target": "ES2017",
"module": "esnext",
"moduleResolution": "node",
For any native class you are extending using TypeScript's extends SomeNativeClass
syntax you will want to decorate. For example:
BEFORE:
iOS:
export class MyPluginDelegateImpl extends NSObject {
Android:
export class ListViewItemClickListenerImpl extends java.lang.Object
AFTER:
iOS:
@NativeClass()
export class MyPluginDelegateImpl extends NSObject {
Android:
@NativeClass()
export class ListViewItemClickListenerImpl extends java.lang.Object {
The NativeClass
decorator is part of @nativescript/core
globals so you should not have to import it from anywhere as it should just be available as long as you're using @nativescript/core
.
In order for your plugin to process that NativeClass
decorator properly you can add a nice extension to typescript and tsconfig.json via ts-patch
package. This is used in combination with a new transformer which comes from the latest @nativescript/webpack
(3.0.0
and above) and is the reason that package is listed in the devDependencies above to use.
npm i ts-patch -D
Modify package.json
scripts to include the install after cleaning the plugin.
Here's an example:
"scripts": {
"setup": "npm i && ts-patch install"
}
tsconfig.json
to support the transformation (add the plugins
section anywhere in compilerOptions
):"compilerOptions": {
"plugins": [{
"transform": "@nativescript/webpack/transformers/ns-transform-native-classes",
"type": "raw"
}]
}
Now when you build your plugin with tsc
any NativeClass
decorated classes will be properly compiled for the NativeScript runtimes.
tns-core-modules
and flatten all imports to come from just @nativescript/core
You can use @nativescript/core
for all your imports now. Some symbols have been wrapped up into convenient rollups to avoid so many generic symbols flying about codebases. You can use this import reference guide to coalesce the usages.
Here's an example:
BEFORE:
import * as app from 'tns-core-modules/application';
import { messageType, write } from 'tns-core-modules/trace';
import { ObservableArray } from 'tns-core-modules/data/observable-array';
import { GridLayout } from 'tns-core-modules/ui/layouts/grid-layout';
import { KeyedTemplate, View } from 'tns-core-modules/ui/core/view';
import { layout } from 'tns-core-modules/utils/utils';
// sample code
const rootView = app.getRootView();
write('Some message', 'a category');
layout.getDisplayDensity();
alert('Hi');
AFTER:
import {
Application,
Trace,
ObservableArray,
GridLayout,
KeyedTemplate,
View,
Dialogs,
Utils
} from '@nativescript/core';
// sample code
const rootView = Application.getRootView();
Trace.write('Some message', 'a category');
Utils.layout.getDisplayDensity();
// you can still use global alert just fine but to better identify {N} dialog usage you can now use the `Dialogs` rollup which contains all the dialog methods and interfaces
Dialogs.alert('Hi');
tns-platform-declarations
and replace with @nativescript/types
In your references.d.ts
:
BEFORE:
/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />
/// <reference path="./node_modules/tns-platform-declarations/android.d.ts" />
AFTER:
/// <reference path="./node_modules/@nativescript/types/index.d.ts" />
You can also include specific platform types as needed if you desire; for example, say you wanted different Android sdk types:
/// <reference path="./node_modules/@nativescript/types-ios/index.d.ts" />
/// <reference path="./node_modules/@nativescript/types-android/lib/android-29.d.ts" />
With NativeScript 7 comes official Angular 10 support. This is important since Angular 10 dropped official es5 support and are also targeting es2017+.
In order to compile your Angular specific plugin parts we can use ng-packagr to assist our build.
npm i ng-packagr -D
A couple interesting things about using ng-packagr
is that the Angular pieces need to be self-contained and hence reference other "outside" dependencies from their actual bundle import package name. For example:
import { MyPluginClass } from '../some/vanilla-plugin-code';
This will not work because ../
is pulling in code into the Angular build which is not part of the "packaged" dist. To fix, that would become:
import { MyPluginClass } from '@nativescript-community/your-plugin';
For this reason there's a couple architecture choices you can make with your plugin organization and is one of several reasons we are introducing a new plugin seed which helps here.
A. You could create a src-angular
folder parallel to your plugin's src
folder and it would have a dependency on your plugin itself.
B. You could keep your angular
folder inside your plugin src
but should structure your plugin in a monorepo style architecture which helps manage dependencies in a smarter way as well as build configuration settings.
We prefer option B above all because it not only elegantly solves that issue but solves many others like simplifying the ease of maintenance for managaing a number of plugin's you develop all from one single source of truth when it comes to dependencies and build configurations.
No matter which direction you go there's one thing you want in the Angular source of your plugin for ng-packagr
. A package.json
with at least the following contents:
{
"name": "nativescript-community-your-plugin-angular",
"ngPackage": {
"lib": {
"entryFile": "index.ts",
"umdModuleIds": {
"@nativescript/core": "ns-core",
"@nativescript/angular": "ns-angular",
"@nativescript-community/your-plugin": "ns-community-your-plugin"
}
},
"whitelistedNonPeerDependencies": [
"."
]
}
}
The name can be any identifier but we're just using a standard flattening of the plugin name and appending -angular
to the end to signal this is the angular part of the plugin.
The entryFile
is utmost important here and it should be named index.ts
for safety. You could name it something else but if it's named the same as the plugin itself then it will fail so just use index.ts
.
Here's example package.json
structure from one of our own internal plugins @nativescript/datetimepicker
:
datetimepicker
/ angular
index.ts
nativescript-datetimepicker.accessors.ts
nativescript-datetimepicker.directives.ts
And the contents of the index.ts
can be seen here.
Notice it imports the vanilla {N} plugin code from it's full plugin barrel name @nativescript/datetimepicker
instead of relative import paths back a directory. This is important to package the angular build properly.
Once ng-packagr does it's build, it default places a dist
directory inside the angular source which the contents can be copied to your plugin's output ready for publishing.
You can kick off the angular source build as follows:
ng-packagr -p angular/package.json
As mentioned we will be introducing a new plugin seed with the architecture in place ready to scale plugin development (as well as drop in existing plugins) and handle details like this better than the prior plugin seed. Look out for an announcement on this in the next 2 weeks.