Is it possible to implement a web worker with TeaVM?

205 views
Skip to first unread message

Bruno Salmon

unread,
Oct 1, 2020, 4:56:18 PM10/1/20
to TeaVM
hi,

I would like to know if it's possible to write a web worker in Java and compile it with TeaVM.

Basically I would like a Java static method to be compiled to a "onmessage" Javascript function. 

I noticed the @Export annotation in teavm-interop but it seems to be designed for WebAssembly and not Javascript. When I compile in WebAssembly, I see "onmessage" in instance.exports but when I compile in Javascript, there is no "onmessage" function in clear, everything is minified, so the js file can't be used as a web worker.

Any idea how to achieve this with TeaVM?

Steve Hannah

unread,
Oct 1, 2020, 6:17:35 PM10/1/20
to Bruno Salmon, TeaVM
You can't just have a static method work as a web worker, as far as I know.  You can use TeaVM to generate javascript that could be used as a web worker.  But this wouldn't be doing any special sharing of runtime code with the main javascript application.  I.e. write your web worker code in TeaVM and compile.  It generates the classes.js file with all the code converted to JS. You could rename this file to something like webworker.js.

Then you could load this as a web worker from either another javascript application, or another teavm application.

Probably not what you were looking for.  

--
You received this message because you are subscribed to the Google Groups "TeaVM" group.
To unsubscribe from this group and stop receiving emails from it, send an email to teavm+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/teavm/baaf3195-f922-4deb-9cc1-363320a49efdn%40googlegroups.com.


--
Steve Hannah
Web Lite Solutions Corp.

bruno....@gmail.com

unread,
Oct 1, 2020, 7:21:43 PM10/1/20
to TeaVM
I'm fine with any working solution. Ok to have a main body empty for the TeaVM compiler. For example something like this would be ok:

public class MyWorker {

    public static void main(String[] args) {
    }

    @Export( name = "onmessage" )
    public static void onMessage(JSObject object) {
        // my worker code here
    }
}

TeaVM successfully compiles this, but the problem is that there is no function called "onmessage" in the generated javascript file, everything is minified despite of the @Export annotation.
So even if I rename the js file, I can't load it as a web worker.

Steve Hannah

unread,
Oct 1, 2020, 7:38:52 PM10/1/20
to bruno....@gmail.com, TeaVM
I would go for something more like:

public class MyWorker {

    public static void main(String[] args) {

           Window.current().addEventListener("message", new EventListener() {
                    public void handleEvent(Event evt) {
                           onMessage(evt);
                    }
            });
    }


     public static void onMessage(JSObject obj) {
              ....
      }
}

I haven't done this myself, but am pretty sure that this will work.  A couple of points that I'm not sure:

1. Window.current() in the worker, I believe is set to return the "self" object of the WebWorkerGlobal scope when run in this context.  If not, you may need to jig something in there (easy to do).
2. In the normal context, the "main()" method is called in the onload callback of the <body> tag.  In a web worker, you'll need to trigger this manually - e.g. by adding a call to the main method at the end of the classes.js file.



Steve Hannah

unread,
Oct 1, 2020, 7:44:10 PM10/1/20
to bruno....@gmail.com, TeaVM
Sorry... I forgot that WebWorkers don't have addEventListener.  You could do something like:

@JSBody(params={"handler"}, script="self.onmessage=handler")
public static native void onmessage(EventListener handler);

Then in your main() method:

onmessage(new EventListener() {

    public void handleEvent(Event evt) {
        onMessage(evt);
    }
});

I think that would work.

Bruno Salmon

unread,
Oct 2, 2020, 5:52:42 PM10/2/20
to TeaVM
Thanks for the trick, I almost got it work. Here is my Java code:

public class MyWorker {

    public static void main(String[] args) {
        setOnMessageHandler((EventListener<MessageEvent>) MyWorker::onMessage);
    }

    @JSBody(params={"handler"}, script="self.onmessage=handler")
    @Import(name = "setOnMessageHandler")
    public static native void setOnMessageHandler(EventListener handler);

    public static void onMessage(MessageEvent messageEvent) {
        log("Received message with following data:");
        log(messageEvent.getData());
    }

    @JSBody(params={"msg"}, script="console.log(msg)")
    @Import(name = "logString")
    private static native void log(String msg);

    @JSBody(params={"js"}, script="console.log(js)")
    @Import(name = "logJs")
    private static native void log(JSObject js);
}

And then the caller code (in JavaScript here):
const worker = new Worker("classes.js");
worker.postMessage({ bar: "Foo" });

I said almost because to make it work, I had to patch classes.js by adding main() at the end the script so the main method is automatically called when loading the worker.

Any idea how to call the main method in a cleaner way?

Steve Hannah

unread,
Oct 2, 2020, 5:59:52 PM10/2/20
to Bruno Salmon, TeaVM
I don't know of a "cleaner" way.  I would just automate the injection of the main() method via some sort of post-processing action using your preferred method (Ant/Maven/Gradle, etc...)

Bruno Salmon

unread,
Oct 2, 2020, 6:26:31 PM10/2/20
to TeaVM
Ok why not, but not very straightforward. 

I would prefer that TeaVM supports the @Export annotation not only for WebAssembly but for JavaScript as well.
Wouldn't that make sense? 

Bruno Salmon

unread,
Oct 5, 2020, 5:53:41 AM10/5/20
to TeaVM
Just for those who may be interested doing the same, here is how to do the patch with Ant (through Maven):

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>append-main</id>
            <goals>
                <goal>run</goal>
            </goals>
            <phase>package</phase>
            <configuration>
                <target>
                    <echo append="true" file="target/teavm/js/classes.js">main()</echo>
                </target>
            </configuration>
        </execution>
    </executions>
</plugin>

Or an alternative solution without Ant:

<plugin>
    <groupId>io.github.olivierlemasle.maven</groupId>
    <artifactId>plaintext-maven-plugin</artifactId>
    <version>1.0.0</version>
    <configuration>
        <files>
            <file>
                <name>teavm/js/classes.js</name>
                <append>true</append>
                <lines>
                    <line>main()</line>
                </lines>
            </file>
        </files>
    </configuration>
    <executions>
        <execution>
            <id>append-main</id>
            <phase>package</phase>
            <goals>
                <goal>write</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Alexey Andreev

unread,
Oct 7, 2020, 5:09:59 AM10/7/20
to TeaVM
There's a cleaner solution. You can create file worker.js with following content:

importScripts("classes.js");
main();
Reply all
Reply to author
Forward
0 new messages