Monthly Archives: November 2019

NativeScript Offline Plugins

There are cases where you might want to package plugins with your app in your version control; like for example you are using a commercial plugin or maybe a proplugin and need to use cloud building.

The simplest setup that I have personally used is in the very root of your application.


You create a folder called plugins. Inside this folder you put any commercial and plugins you need to keep with the apps source. For example one of my projects looks like the above picture, I have 5 plugins that are actually in version control with the application. So anyone checking out the source code from git will get all these plugins with the application.

To use these plugins in your NativeScript app, in the root directory you type "tns plugin add ./plugins/nativescript-compress-0.0.1.tgz" you need to type the ENTIRE name including the extension.

How do I download them

So say I want to have the proplugins/nativescript-dialog plugin.

npm pack @proplugins/nativescript-dialog
will download the latest .tgz file into the current directory for you.

PSA: NativeScript 6.20 - Is a Breaking Change release!!!

Upgrading to 6.2.1 and the latest Nativescript-angular (if using angular) and the latest proplugins; should fix MOST issues that 6.2.0 had. However, I still highly recommend the webpack rewrite rules at the bottom of the post to solve ALL the issues.


NativeScript follows the SemVer process; so normally they would have bumped a major version on breaking changed. This break is mostly an un-attended side effects of a couple things. And unfortunately they didn't realize it broke things, so we now have 6.2 which breaks things... Oh well, mistakes happen, life will go on. And so this post is to make you aware of what things you want to change when you upgrade to NS 6.2.0...

To Upgrade or not to Upgrade, that is the question!

I do want to make sure I am VERY clear -- I do NOT think this was a bad change, nor do I think NS 6.2 is a bad release! My apps will be upgraded to it. It offers a lot of good features and fixes. It is just that being aware of the issues will help you make sure you don't have any issues with it when you do upgrade to it.

Lets dig in to the issue.

So lets get some details, first about 3 major versions ago in 2017, my business partner and buddy Nathan Walker proposed on issue 4041 to move the nativescript tns-core-modules into its own namespace. @nativescript along with other nativescript pieces being put into that parent namespace...

So based on this you might guess where the source of the issue lies now.

If you read the above issue; you will notice Peter (another awesome developer) and I both recommended that they do this in the 6.0 release. I personally felt like a hard break would be the safest course of action. Just eliminate tns-core-modules totally. Since they already were totally breaking the build tools, and also breaking other plugins things in v6.0 -- they might as well add this to the pile so we deal with all the breaking changes at one time in plugins. Unfortunately, our advice wasn't taken, or it was missed and so here we are with a very interesting breaking change that I honestly did NOT foresee when I recommended they do it in the initial 6.0.

There are three different issues here that stem from the same cause:

First, if you are using NativeScript-Angular and use any third party plugins -- you might find that they are failing because NativeScript-Angular was moved into the @nativescript/angular namespace. The compatibility imports for the old namespace; is apparently missing a couple redirects so plugins trying to load them fail. I'm sure these plugins will be updated shortly; but right now they are broken. (This has been fixed in the latest version of NativeScript-Angular)

I do NOT know yet if NS-Vue, NS-Svelte, or React-NativeScript have any issues; but odds are a fairly low with them because they are third party and were not moved into the @nativescript namespace, so apps depending on this probably are NOT affected directly (other than by any other plugins.)

Second, because of the design of the tns-core-modules redirect, the redirects waste a little bit of running memory. If your app or plugin imports tns-core-modules/BLAH (which all apps do before 6.2) then webpack will give you Object #1 tns-core-modules-blah, however, when a plugin or the core modules loads @nativescript/core/BLAH you get Object #2 nativescript-core-blah -- they LOOK the same but they are different objects each using memory. This does increase a little bit some parsing and memory that the V8 has to use because you now have basically more than a single framework in memory. Some of this is mitigated based on how objects are tracked; but some of it isn't. So this creates some other un-intended side effects of little bit more GC pressure, more ram usage, and the nasty pitfall we will discuss later...

Finally, if you are using any plugin that does any sort of class monkey patching, they will now all fail -- in this category is things like my very own nativescript-platform-css, nativescript-orientation. The reason why is because of the above allocation issues. Object #1 != Object #2. monkey patch Object #2, doesn't monkey patch Object #1. This is a corner case; as it requires you to actually replace the class (i.e. the top level object). So any plugins that rely on nativescript-global-events which extends and enhances the page class; were affected.

As an aside: The good news is virtually every one of my plugins on https://proplugins.org has been updated to have a NS 6.2 version. If you find a plugin that no longer works after you updated to 6.2; let us know and we will attempt to get it fixed. The 6.1 -> 6.2 fixes are very minor; so they can be rapidly put into the plugins.

One final note -- Please see the method to update the webpack at the bottom to have webpack rewrite tns-core-modules to @nativescript/core this is still highly recommended as some plugins will still be broken if you are still using the tns-core-modules redirect version.


The low level nitty gritty details - Proceed with caution.

For those who are interested in the nitty-gritty; we will walk through why #2 and #3 occur in NS 6.2. Please note this is VERY simplified -- The actual TS -> JS code is a bit more complex and a wrapped object. However, this should give you an idea of the why's...

V8 basically keeps a object map of how a object looks in memory; so if I have file "Blah" with class HiYa -- that looks like this:

export class HiYa {

   function hi() { /* do something */ }

}

When it exports it has a unique signature. In NativeScript this is compiled down to ES5 code. So this will basically look like this when all said and done:

function HiYa() {} 

HiYa.prototype.hi = function() { /* do something */}

module.exports = HiYa;

Now when I do a const HiYaRunnerMaster = require('@nativescript/core/blah'); The Blah.js file is loaded into memory; parsed and then a runnable object representation of this is passed back to HiYaRunner, lets call this HiYaRunnerMaster. So far so good.

Now for the NativeScript tns-core-module redirects / compatibility layer.. This is the exact code that you see in tns-core-modules/* in 6.2... It basically redirects to the @nativescript/core version of the file. Seems simple, and unless you are paying close attention you might not even see why this actually is a problem.

function __export(m) {

    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];

}

Object.defineProperty(exports, "__esModule", { value: true });

__export(require("@nativescript/core/blah"));

So when I do a const HiYaRunnerFromTNS = require('tns-core-modules/blah'); it loads the above code and runs it. . It first defines the __export function, then runs that function on what it gets from the require of @nativescript/core/blah . Seems simple enough. Lets break this down into each step to what the v8/JavaScriptCore engines do.

  1. const tempObject = require('@nativescript/core/blah'); -- THIS object is the same/identical HiYaRunnerMasterObject as we got earlier when we ran it. Every time we do this specific require statement, we get the exact same object. Which is what we want!
  2. __export(tempObject); This runs the export function, passing in our HiYaRunnerMasterObject which so far doesn't seem like an issue.
  3. for (key in HiYaRunnerMasterObject) { we are looping through every property in HiYaRunnerMasterObject
  4. if (!exports.hasOwnProperty(key) verify the exports variable doesn't have this key property already.
  5. exports[key] = HiYaRunnerMasterObject[key]
  6. And behind the scenes exports is passed to the HiYaRunnerFromTNS at the end.

Did anyone else see what happened in step 5? First, exports is a 100% BRAND NEW OBJECT. (our Object #2). We then COPIED all the properties/functions from Object #1 (HiYaRunnerMasterObject) onto this new Object #2 (exports). Each copy is actually a new piece of memory that point to the old object's key value. So the new exports[Key] = old.value. in simplistic terms has two separate memory slots; "Key" and Value". So exports[key] is always a new memory pointer (of key) that either points to the original object's [key]; or to its own copy of a un-boxed object. So the memory usage could be 50% of the original object to the exact same memory usage as the original object, all depending on what the values (boxed or unboxed) are. In NativeScript it should be much closer to adding the 50% additional memory, not the 100% as a worst case number as almost all values on a nativescript key are objects.

At the end, in step 6 we used the brand new object to assign to HiYaRunnerFromTNS. Whoops; so we now have TWO different objects in memory with two different sets of memory usage.

This means that if you monkey patch the HiYaRunnerFromTNS copy; you are not actually monkey patching the actual original and running version. So in several of my plugins, this caused them to break as the monkey patching basically never occurred as far as the running copy of NativeScript was concerned.

In addition their is one more potential gotcha; I'll make a simple example:

let Obj1 = {a: {value: 1}}; // Original Object
let Obj2 = {a: Obj1.a}; // Copied key object

If I do Obj1.a.value = 2; then Obj2.a.value === 2 because both a's point to the exact same memory location with a "value" key in it . However, if I do Obj1.a = {value: 10} then Obj1.a then creates a brand NEW object which also has a value key; of which it has the value of 10 in it. And Obj2.a continues to point to the OLD original object with the value = 1. So even though your objects initially started pointing to the exact same value in memory. A new object assignment will replace just that objects location.

Why does this matter? When since the running instance of NativeScript is the @nativescript/core versions of the object. And your applications code will all currently require tns-core-module versions; you DO initially have two objects but they should all start out pointing to the same memory locations. Just like our example earlier (Obj1.a = Obj2.b) However, if you change a value that is NOT using a setter -- then the value you just changed won't be propagated to the running version, and you will have Obj1.a = {value: 10} and Obj2.a = {Value: 1} and you won't have a clue you just did a noop statement.

I do want to say the above potential gotcha scenario is very UNLIKELY, all code that I can think of runs through a setter; a setter function should be getting and setting the original object's value, and thus they shouldn't go out of sync. But corner cases can and do occur. So my recommendation for anyone updating to NS 6.2 is to do...

Recommendations

  1. Long term, replace ALL tns-core-modules/ with @nativescript/core/ and all nativescript-angular/ with @nativescript/angular/ everywhere in your app. No longer have ANYTHING pointing to tns-core-modules/ or nativescript-angular -- pretend those old namespaces no longer exist and were deleted.
  2. Update to the latest @proplugins if you are using any @proplugins, as most of them have already made the transition, as this will at least save you time on that aspect.

Short term tip (and highly recommended until they have a solid fix):

Richard Smith, posted a good tip that can quickly get you working -- you can add a rule to your `webpack.config.js` file. Find the alias section and add:

   
  "nativescript-angular": "@nativescript/angular",
  "tns-core-modules": "@nativescript/core",

So it looks like:

  
alias: {
      "~": appFullPath,
      "nativescript-angular": "@nativescript/angular",
      "tns-core-modules": "@nativescript/core"
},

NativeScript 6.2.0 Released

NativeScript 6.20 has several fixes, and finally added the long awaited Scoped packages. UPDATE Scoped packages apparently breaks some things. See here for more info on how to deal with it.

Scoped packages; are where you can now do

const label = require("@nativescript/core/ui/label" rather than the older const label = require("tns-core-modules/ui/label");

This makes things a lot cleaner and over time more times should end up in the @nativescript namespace. Currently in the @nativescript namespace is:

  • Core Modules
  • Theme
  • Angular

Core Modules

Since they added the ns-root, ns-landscape, ns-phone, etc; in the prior version of NativeScript they have added even more css functionality in this version. ns-light and ns-dark are now added values which you can use to select specific theme color rules. They are added depending on the devices theme. Along with that change, more css optimizations, including hsla color support were added. A long awaited and very welcome fix to allow values to be reset to default when css rules are disabled was also finally done. Overall just the CSS fixes are worth the price.

Another cool change is they updated they revampled the android transition code. Should be better and more customizable. In addition Android also got ContentInset properties on the ActionBar. And iOS got selectedSegmentTintColor on the Segment bar.

Finally, the awesome community PR that was snuck into this release is Peter Staev's async read/write addition. readAsync and writeAsync allow async access to the file system.

CLI

The CLI team has fixed several outstanding bugs; things like Asset generation, several webpack issues, cocoapod fixes.

V8

I'm going to create this new v8 Section, because Android and soon iOS will both be using the same v8 engine. v8 was upgraded to v7.7 which adds:

  • Lazy feedback allocation - this reduces the memory load of the v8 engine, which is always good to have on mobile devices

Android

The Android team kept busy this month;
- They improved the error logging.
- Added Kotlin extension function support
- Added JSONObject support
- Upgraded to V8 7.7 (See v8 section)

IOS

Fixed a Mashalling issue from "unsigned char *"
Fixed losing some exceptions in TS from runtime

A lot of changes to the new v8 version of the iOS runtime that is still in alpha testing to eventually replace the JSCore version... This new runtime has pretty close to the same feature parity as the Android versoon. I'm sure the iOS team would LOVE you all to test out this runtime on your apps to verify they work properly -- you can do so by using tns platform add ios@alpha-v8 to tell it to use the v8 runtime.

Updating NativeScript

To get updated; you first need to do:
npm i -g nativescript@latest

To update a project; you need to do the following:

Latest Runtimes:
tns platform remove android && tns platform add android@latest
tns platform remove ios && tns platform add ios@latest

Latest Core modules:
npm r tns-core-modules --save
npm i tns-core-modules@latest --save

To install Webpack support:
npm i nativescript-dev-webpack@latest --save-dev

To install latest NativeScript Angular plugin
npm i nativescript-angular@latest --save
You will then need to install the actual angular bits; which as of this post v6 is currently supported.

The addition of t additional analytics/tracking to the CLI reminded me; you can disable it permanently; if you value your privacy by doing:
tns usage-reporting disable && tns error-reporting disable