NativeScript / NativeScript

⚡ Empowering JavaScript with native platform APIs. ✨ Best of all worlds (TypeScript, Swift, Objective C, Kotlin, Java, Dart). Use what you love ❤️ Angular, Capacitor, Ionic, React, Solid, Svelte, Vue with: iOS (UIKit, SwiftUI), Android (View, Jetpack Compose), Dart (Flutter) and you name it compatible.
https://nativescript.org
MIT License
24.15k stars 1.64k forks source link

Typescript class conversion silently fails when using ES6 imports #8573

Open belvederef opened 4 years ago

belvederef commented 4 years ago

Environment Provide version numbers for the following components (information can be retrieved by running tns info in your project folder or by inspecting the package.json of the project):

Describe the bug

When defining a class that extends a native class (in my case an Android service) and using an ES6-style import at the top of the file, the class does not get converted to Java and is not included in the /platforms folder. No error is given at bundling time.

To Reproduce

import { Utils } from '@nativescript/core';

@JavaProxy('com.belvederef.MyNotificationListener')
class MyNotificationListener extends android.service.notification
  .NotificationListenerService {
  constructor() {
    super();
    return global.__native(this);
  }

  onNotificationPosted(
    sbn: android.service.notification.StatusBarNotification,
  ): void {
    ...
    const context = Utils.android.getApplicationContext();
    context.sendBroadcast(intent);
  }

  ...
}

Expected behavior The class should be added to the platform folder or, if not possible to bundle with ES6-style imports, raise an error.

Additional context

I was able to solve the problem using node require() imports in my method as below:

  onNotificationPosted(
    sbn: android.service.notification.StatusBarNotification,
  ): void {
    ...
    const Utils = require('@nativescript/core').Utils;
    const context = Utils.android.getApplicationContext();
    context.sendBroadcast(intent);
  }
}

ES6-style imports are shown as working on the documentation when extending native classes. I am not sure whether the error is caused by the object destructuring in the import, but when I use the same import with destructuring somewhere else in the app and it works.

For searchability's sake, I got to know about the error due to a java.lang.ClassNotFoundException raised at runtime.

NathanaelA commented 4 years ago

First; note your issue states ES6, however, your code appears to be TypeScript; if you are using TypeScript you MUST target ES5. If you are using ES6, then the annotations do NOT work; as they are not actually part of the ES6 spec...


This is a known gotcha, but it is a different cause. :grinning:

The docs here: https://docs.nativescript.org/core-concepts/android-runtime/binding-generator/extend-class-interface show how you are supposed to do it in JS/ES6.

When NativeScript was first created ~5 years ago ES6 was still not widely supported, and as such the issue is actually with this line:

class MyNotificationListener extends android.service.notification

If you use TypeScript and it is transpiled to ES6 or just using ES6; then actually the issue is the extends; the JS engine is not aware on how to use the "extends" to extend a Java (or iOS native class). So it fails...

You need to use the .extend() command to extend a native object on both android and ios.

One idea that has been considered is to use annotation when they are available in ES6 to be able to supply the additional information that is needed, but in the meantime, this is one of those annoying gotcha's. If using typescript you can see it already uses annotations; but again typescript must target ES5 so that it is transpiled to use extend()...

belvederef commented 4 years ago

@NathanaelA Thanks for your response and I apologise for not having explained the situation in a clearer way. With "ES6 import" I was really trying to say ES6 style import, just to oppose it to ES5/Node style imports (which use the require keyword).

I am indeed using Typescript and transpiling to ES5, and can confirm that I am using the extend keyword throughout my project, which works perfectly. I even tried to replicate with a new project created straight from the cli and can confirm that removing the line

import { Utils } from '@nativescript/core';

and replacing it with

const Utils = require('@nativescript/core').Utils;

allows the corresponding Java file to be generated and saved to the /platform folder. Without this "trick", the Java file does not get generated. The issue is perfectly reproducible, I tried a number of times, but it could be surely related to something happening internally that I am not aware of. However, the same usage is shown in the docs, therefore, it was quite hard to find this solution.

NathanaelA commented 4 years ago

That is very strange then; if you are using TypeScript the import should automatically be transpiled to a require statement for you.

Can you tell me which version of TypeScript you are using?

Any chance you can submit a simple demo test app that shows this issue...

belvederef commented 4 years ago

Here is a barebone project that reproduces the error https://github.com/belvederef/nativescript--8573. Have a look at the file /app/services/MyNotificationListener.ts; if you run the project as is, it will not generate the Java file but when you remove import { Utils } from '@nativescript/core' and uncomment this line you can see it will.

NathanaelA commented 4 years ago

Hi I tested your app; you are correct it is missing building this.

However, the reason why is not directly because of the import vs require, not really sure how the require causes it to work for you. The issue is because it is a Vue app, and TypeScript support for Vue is really early from my understanding. Something in the vue webpack configuration file for Vue is not picking it up properly.

I did: tns create test8573 --ts copied your services directory over to the new app into the same "services" location. tns build android

Went into the build directory and it was built properly. image

belvederef commented 4 years ago

I understand, thanks for clearing that up. But shouldn't something be done about this? At least reference it somewhere in the docs? It seems to happen only when extending native classes.

NathanaelA commented 4 years ago

@belvederef - Actually, I'm not 100% sure that is the source; I haven't had time but I planned on created a real quick VUE TS app to verify...

NathanaelA commented 4 years ago

Just a follow up it seems to be a combination of both the vue version of the webpack config and something in the vue version of the TSConfig settings. I made a couple changes to the vue webpack config to take features from the straight ts webpack and then moved the tsconfig from the same app; and it built the JAVA code properly...