Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript engine: update J2V8 or migrate to graalvm or something else #556

Closed
nedtwigg opened this issue Apr 12, 2020 · 13 comments · Fixed by #606
Closed

javascript engine: update J2V8 or migrate to graalvm or something else #556

nedtwigg opened this issue Apr 12, 2020 · 13 comments · Fixed by #606

Comments

@nedtwigg
Copy link
Member

J2V8 is having trouble keeping up with the latest node/npm versions, and has given up (for now) on supporting them on desktop (now android-only) eclipsesource/J2V8#441. That's not a huge problem, we still run fine, but the latest version of prettier we can support is 1.19.0 - we'll need to migrate to something else in order to support newer versions.

One possibility is frontend-maven-plugin which has a non-maven-exclusive lib. J2V8 is unique in that it allows us to call node.js functions natively, anything else will be a big performance hit for our usecase.

@nedtwigg
Copy link
Member Author

nedtwigg commented May 6, 2020

Another good bet is graalvm. We already have a few steps which require Java 11+, it would be okay to start requiring Java 11+ for our javascript-based plugins. We currently build and ship using Java 8. If someone makes a working PR that requires us to bump minimum to Java 11, please feel free to do so.

Once we have a working PR, we can evaluate whether we want to bump the minimum, or factor-out this portion to a separate module to keep Java 8 support.

@nedtwigg nedtwigg changed the title Update J2V8 or migrate to something else javascript engine: update J2V8 or migrate to graalvm or something else May 6, 2020
@simschla
Copy link
Contributor

Another idea I had (before going the J2V8 path) was to use actual native node.js to run a rest-service. That service could allow a REST-Interface to call prettier and tsfmt inside the node process from our spotless run inside the jvm. Kind of like the gradle daemon: Spawn a service with node, that can then be used to format the code for us by calling a local rest interface from the spotless build. That way I would not expect a massive performance hit, since the node-rest-service would be a long-running process, not one that we would need to start per file (which would kill the performance for sure).

@caoccao
Copy link

caoccao commented Sep 14, 2021

An alternative way is to use an embedded Node.js runtime Javet. It brings the genuine Node.js environment and supports JDK 8.

@nedtwigg
Copy link
Member Author

Javet looks fantastic, J2V8 was very helpful, glad that somebody else picked up that idea with modern V8. Looks like it doesn't bundle npm though, so we'd be on our own to grab dependencies from npm. At this time, I don't see a way that Javet simplifies our overall situation, but feel free to open enhancement-request issues or to discuss further here :)

@caoccao
Copy link

caoccao commented Sep 14, 2021

Javet looks fantastic, J2V8 was very helpful, glad that somebody else picked up that idea with modern V8. Looks like it doesn't bundle npm though, so we'd be on our own to grab dependencies from npm. At this time, I don't see a way that Javet simplifies our overall situation, but feel free to open enhancement-request issues or to discuss further here :)

Thank you for the feedback.

Interestingly, I thought the purity of the Node.js mode is a feature rather than a drawback.

  1. Managing dependencies belongs to application. npm, yarn, whatever. Javet just loads it in embeded Node.js.
  2. Javet doesn't depend on any libraries other than JDK + Node.js + V8. So it deliberately doesn't integrate npm. I also own another project Javenode aimimg at polyfilling V8 mode with Node.js features. That project can depend on more external libraries to provide fancy features.

I did research agaist Embedded Node.js vs. Node.js with gRPC. I have to admit the performance gap is like ~4M calls/s vs. ~10K calls/s. The detailed performance test result is at here. So, I quickly abandoned the idea of making gRPC calls to bring Node.js ecosystem in JVM.

@nedtwigg
Copy link
Member Author

the performance gap is like ~4M calls/s vs. ~300

Wow! At first this seems like a ~10,000x speed improvement, but I think most of our wait time is actually waiting for prettier to finish its calculations within node. Say a project has 1,000 files, we're only making ~1,000 RPC calls anyway, and the reason we're waiting 30 seconds isn't RPC overhead, it's just calculation time within node.

the purity of the Node.js mode is a feature rather than a drawback.

I agree! Especially when you're deploying something, you almost definitely have some packaging/bundling step.

For a dev tool like Spotless, it's a hard requirement for us to say "resolve [email protected] and its dependencies". Right now, that means we need to figure out how to get npm, and if we have that then we also have a matching node distribution with it.

I find managing node/npm to be a nightmare, Gradle does such a great job managing JRE versions for you. Might be an opportunity for JVM-friendly npm-package-resolution library based on your work. One of our worst problems right now is #766, and there's no good reason why it has to be so slow, besides that npm is painful and we haven't taken the time to make it more friendly.

@caoccao
Copy link

caoccao commented Sep 14, 2021

Yeah, one of the pain points in Node.js is missing libraries.

However, Javet allows Java applications to listen to the module resolve event. Either import { *** } from 'a.js' or require('a.js') can be intercepted. This doc presents how to do that. This Java file is the module resolver in Javenode that supports import { *** } from 'a.js'. So, with Javet, applications may leave npm behind and invent a new way of resolving libraries.

By the way, I think one of the fancy things here is Java applications may resolve that in a multi-threaded manner to speed things up via Javet.

@nedtwigg
Copy link
Member Author

Javet allows Java applications to listen to the module resolve event ... may resolve that in a multi-threaded manner to speed things up

Is there an issue or somesuch I can subscribe to so that I get notified when somebody builds something along those lines?

@caoccao
Copy link

caoccao commented Sep 14, 2021

Is there an issue or somesuch I can subscribe to so that I get notified when somebody builds something along those lines?

So far, there is no one asking for multi-threaded module resolution. I think mainly because import() is not popular yet as dynamic import can substantially benefit from multiple threads.

Regarding the rest, you may watch Javet and Javenode.

@caoccao
Copy link

caoccao commented Sep 15, 2021

By the way, I just thought it over. It might be possible with the following trick.

  1. Convert require and static import to dynamic import
  2. Put all dynamic import promises in an array and call Promise.allSettled().
  3. Put the rest of business logic in the final then().

Here is the pseudo code.

// const a = require('a.js');
// import { b } from 'b.js';

let a, b;

const promiseA = import('a.js').then(result => a = result); // Handled by thread 1
const promiseB = import('b.js').then(result => b = result); // Handled by thread 2

// Wait for all modules to be loaded simultaneously.
Promise.allSettled([promiseA, promiseB]).then(/* business logic */);

Node.js supports dynamic import, but it doesn't allow intercepting that dynamic import. Luckily, Javet allows this trick to work on top of genuine Node.js because it is able to inject the module resolver in Node.js. In my humble opinion, this would be much better than npm as it feels like the applications really take back the control over module resolution.

Hope this makes sense. Please let me know if you have any questions.

@caoccao
Copy link

caoccao commented Sep 15, 2021

Also, sorry for misleading you with a typo. It should be ~4M calls/s vs. ~10K calls/s.

@nedtwigg
Copy link
Member Author

When you say "prettier:4.0.3", there are a ton of dependencies that need to get downloaded. Whether it's multithreaded or not, async or not, doing that resolution and caching them locally is not trivial, and is definitely outside the scope of Spotless. If that capability is available and ready to use as a library somewhere, then I think it would be appropriate for Spotless to adopt.

@caoccao
Copy link

caoccao commented Sep 15, 2021

I'm not sure if any of the following might satisfy Spotless.

As Spotless uses gradle, it might be quite easy to have gradle utilize them for npm package resolution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants