konsoletyper / teavm

Compiles Java bytecode to JavaScript, WebAssembly and C
https://teavm.org
Apache License 2.0
2.64k stars 265 forks source link

Importing external remote modules #842

Closed aghasemi closed 10 months ago

aghasemi commented 1 year ago

Consider the following Javascript code:

import Toastify from "https://esm.sh/toastify-js@1.12.0"

If I want to convert it to the equivalent TeaVM code, it should be something like:

@JSBody(
        script = "Toastify({text: \"This is a test toast\",duration: 3000,}).showToast()",
        imports = @JSBodyImport(
                alias = "Toastify",
                fromModule = "https://esm.sh/toastify-js@1.12.0"))
    private static native void callModule();

The code however doesn't work, saying that the object Toastify does not exist (it seems):

Uncaught TypeError: $rt_import_0 is not a function
    at ia_Client_callModule$js_body$_1 (classes.js:1053:10)
    at ia_Client_main (Client.java:53:1)
    at classes.js:607:19
    at $rt_startThread (classes.js:1752:22)
    at classes.js:606:13
    at onload (3000/:8:25)

Also Network tab of Devtools shows no connection has been made to unpkg.sh (again I may be wrong). In general, does TeaVM support such "remote" imports? If yes, is there an example?

And in general, how does calling third-party Javascript libraries from TeaVM work? Should we dynamically add <script> tags to the HTML head for loading the library and then calling it in native snippets?

Thanks

aghasemi commented 1 year ago

Update: To solve this, one solution is something like the following code,

 import("https://esm.sh/toastify-js@1.12.0").then( ({default :Toastify}) => { 
            Toastify({text: "This is a test toast",duration: 3000,}).showToast() 
        })

, which I can run from a <script> tag in index.html (without type="module"), but not in the script argument of a JSBody, because Uncaught TypeError: $rt_globals.import is not a function. Is there another step I need to do to make this work?

P.S. Apparently, the problem boils down to how I can prevent TeaVM from appending $rt_globals. to the import statement in the above snippet.

P.S.` Is it this line of code whose changing can fix this?

konsoletyper commented 1 year ago

Why don't you just include toastify.js with <script> element and use it from global name? Where did you get the library? Is it this one? So documentation states that you need following:

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>

And then you don't need any imports

konsoletyper commented 1 year ago

TeaVM's import functionality is a little-bit old-fashioned. Currently TeaVM only supports ES5 output. It's a subject of change in the future, but right not it can't use ES6 import. Instead, it generates common.js compatible imports, which can be used with node.js and webpack.

aghasemi commented 1 year ago

Why don't you just include toastify.js with <script> element and use it from global name? Where did you get the library? Is it this one? So documentation states that you need following:

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>

And then you don't need any imports

Injecting a script tag is fine and works for this library, but not easily for all others (I'm looking at Transformer.js for example). Looking forward to having the ES6 modules.

Now another thing: A very "hacky" solution to have import parsed verbatim would be to wrap it in an eval. I tried and it worked, but it seems TeaVM parser only recognises single-line, double-quotation strings, and everything else (including multi-line or template strings) is rendered as eval(), i.e. evaluating nothing (as I saw in classes.js).

Hence, this works:

eval("console.log(99) ")

, but this doesn't:

eval(`console.log(99)`)

Is it also part of the consequences of supporting older Javascript standards?

Thanks

konsoletyper commented 1 year ago

Yes, I as said, TeaVM only works with ES5. But I don't understand this thing with eval. How are you going to use it to work-around ES6 module issue?

aghasemi commented 1 year ago

If I put the code snippet with import() inside an eval, then TeaVM parser will not prepend it with $rt_globals.

konsoletyper commented 1 year ago

I can suggest another workaround: you can write JavaScript code that imports everything you need and puts it into global objects, than access these objects from TeaVM.

aghasemi commented 1 year ago

Yes. I'm thinking of that exactly :)

For the context, assume I want to write an independent Java library on top of TeaVM, not a one-time thing for my current project. Hence everything should be dynamic and modifying index.html is not an option.

aghasemi commented 1 year ago

One working example is this:

private static final String tstfy =  """
        const doc = window.document;

        var head = doc.head;
        var script = doc.createElement('script');
        script.type = 'module';
        script.innerHTML = 'import Toastify from "https://esm.sh/toastify-js@1.12.0";Toastify({text: "This is a test toast",duration: 3000,}).showToast();console.log("Loaded ToastifyJS module");';
        script.async = false

        // Fire the loading
        head.appendChild(script);
    """;

    @JSBody(script = tstfy)
    private static native void callToastify();