opal
opal copied to clipboard
Feature: Interact with JS Object directly without having to manually wrap with Native
Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
I get a JS object response
object from an HTTP Ajax library I'm using (jquery-opal).
If I call response.body
, I get a JS object, and if I try to dig into its data the simple way any developer might attempt by simply calling response.body.data
, Opal crashes telling me Uncaught TypeError: response.$body(...).$data is not a function
This is extremely discouraging to an Opal Newbie.
I realize I could wrap response.body
with Native
and then call data
like Native(response.body).data
, but that is not intuitive, and having to worry about low-level details like that while I am solving business problems with Opal is distracting and can be a big waste of time.
In JRuby, I can interact with any Java object directly from Ruby without having to wrap anything explicitly. response.body.data
does work in JRuby. It's a shame it doesn't work in Opal out of the box given that JS is a closer language to Ruby with both being dynamically typed, unlike Java, which is statically typed. JRuby also handles edge cases like Ruby implementing a method that is already implemented in Java. The developer could still dig into the Java version of a method with special syntax (java_send
) or inherit/override Java method with Ruby behavior.
I realize there might be performance implications for supporting this, but that concern can be resolved by following the 80/20 rule:
- Support an Opal configuration option for interacting with JS objects using
Native
manually if extreme performance optimizations are needed - Make the Opal default being able to simply and intuitively interact with JS objects from Opal Ruby through direct invocation syntax without having to wrap anything with
Native
explicitly or manually.
This is a major design flaw in Opal that might have been 50% responsible for the lack of adoption of Opal over the last decade. If this was handled already, the selling pitch for Opal Ruby would have been a lot stronger to a point where it's a no-brainer and nobody could say no. Ideally, we don't want to give people any excuses or reasons to see Opal Ruby in the Frontend as less convenient to use than straight JS.
Describe the solution you'd like A clear and concise description of what you want to happen.
Allow the ability to interact with JavaScript objects directly from Ruby without using Native
explicitly or manually, just like JRuby allows interaction with Java objects directly from Ruby.
Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.
For now, I am able to resolve this issue by wrapping objects with Native
manually or sometimes adding the wrapping functionality to an Opal library gem directly when possible to save the user from having to do the wrapping themselves.
Additional context Add any other context or screenshots about the feature request here.
You can learn from how JRuby handles calling Java objects from Ruby: https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
The main idea I have regarding this issue, which I hope I will be able to implement, is as follows:
- For Opal 2.0, deprecate the backtick operator (except if called with a flag) - this is mostly done.
- Rename the
JS
library toOpal::Raw
. This way we ensure everyone knows that we are dealing with "Raw" values. And we free the JS namespace (already worked on in #2524; namely I also wantJS
free for a future Ruby-WASM compatibility library but I won't be writing about this in this post) - Since those two interfaces are basically the only ways to get raw values, we basically ensure that each raw value returned from a library is a library bug.
Furthermore:
- Provide an alternative wrapping access library as
Opal::JS
. This will be a next generation of whatNative
currently is.- This library would - just like Native - have two purposes:
- Direct access to JavaScript world of types, automatically converting between Ruby and JS semantics.
- So, for instance an Opal proc would always transparently wrap/unwrap the values
- For importing, we would have something like:
jquery = Opal::JS.import("jquery")
which would compile into something likeimport _tmp from "jquery"; var jquery = Opal::JS.wrap(_tmp)
(similarly, there would be a raw call likeOpal::Raw.import("jquery")
)
- Provide a way to create Ruby-idiomatic bindings from scratch, just like Opal::Browser uses Native
- Direct access to JavaScript world of types, automatically converting between Ruby and JS semantics.
- This library would - just like Native - have two purposes:
I have thought of many other names, but after all, I believe that we should make everything Opal-specific go under the Opal
namespace (as described in #2604). Raw
gives us a way to clearly say: don't use this interface unless you know what you are doing (use JS
instead). It makes the programmer choose JS
over Raw
and not let's say JSWrap
over JS
.