Opal 1.3

We have released Opal v1.3.0 a week ago and today we are releasing Opal v1.3.1 containing a few last minute fixes. We plan Opal v1.3 to be supported with a best-effort principle for a longer term, backporting bugfixes.

This is quite a big release with a special focus on developer tools and error reporting improvements.

Preliminary ES Modules support

Although we didn't yet fully integrate with Webpack as originally planned, Jan Biedermann (of the Isomorfeus project) contributed some preliminary ES Modules support.

If you use Isomorfeus you can take advantage of it with its ESBuild integration and the newly added autoload hooks, which finally works without requiring a patched version of Opal.

If you don't use it you can still roll your own integration, making autoload dynamically fetch modules from the server as they're imported.

We are planning to work on a proper import and npm package support for the next release, along with Ruby 3.1 support.

Newly supported language features

We already support a lot of Ruby 3.0 features. This release fills a few missing gaps, most of which will only be used in very niche usecases.

Refinements

Refinements are now mostly supported. If you don't like monkey patching, this feature can improve your codebase.

binding

Binding allows you to take a peek at local variables in a given scope. You probably most likely know it for binding.irb or binding.pry - while we don't support those yet, it's now only a matter of time when it gets implemented - all prerequisites are there already.

autoload

Autoload statements will cause a file to be included inside your Opal bundle, but won't load it instantly. We moved a bigger deal of corelib to benefit from faster load times.

This support can also be used to load files dynamically, like opal-zeitwerk does in Isomorfeus.

retry

Due to an asynchronous nature of JavaScript, it probably won't have much use here. But still, you can now use this statement to restart the code in a rescue clause.

super improvements

The following code used to call a super function with (a,b,c) arguments, now it correctly calls it with (4,b,c). Also a few more edge cases were corrected.

def method(a,b,c)
  a = 4
  super
end

stdin and gets support

We have now a larger synchronous IO support. gets is implemented on a browser via prompt (aka that annoying alert popup with a text field), on other platforms, including headless chrome, it bridges stdin properly.

Reminder: for basic support of other platforms than browser, you need to require "opal/platform". For extended support of NodeJS, you need to require "nodejs".

$ opal -ropal/platform -e 'p gets'
123
"123\n"

Implementing gets is all that's needed to make a runner able to be ran in the REPL mode

Global variable aliases

Certainly a lesser known feature of Ruby. Did you know it's possible to write this:

alias $PROGRAM_NAME $0
$PROGRAM_NAME = "abc" # This changes $0 as well!

Opal has you covered.

Flip-flop

This Perl-inspired feature was almost removed in Ruby 3.0. But due to some interest, it was actually fixed. Opal now supports this as well! Have you never heard of this feature? That's fair, it mostly isn't present in production code, but can be used for writing unreadable code like this:

a=b=c=(1..100).each do |num|
  print num, ?\r,
    ("Fizz" unless (a = !a) .. (a = !a)),
    ("Buzz" unless (b = !b) ... !((c = !c) .. (c = !c))),
    ?\n
end

Toolkit improvements

opal-repl

The opal-repl command line tool gained a number of improvements:

  • Windows support
  • Colored output
  • Support for printing native JS Objects / null / undefined
  • Pry-like ls support
  • Support for everything that opal command line tool supports
  • Support for multiple runners: nodejs, chrome, gjs, quickjs, miniracer (used to be miniracer only)
$ opal-repl
>> ls 123
Comparable#methods: between?  clamp
Numeric#methods: __coerced__  clone  conj  conjugate  div  dup  i  imag  imaginary  polar  pretty_print  pretty_print_cycle  real  real?  rect  rectangular  step  to_c  to_json  to_n
Number#methods: %  &  *  **  +  +@  -  -@  /  <  <<  <=  <=>  ==  ===  >  >=  >>  []  ^  __id__  abs  abs2  allbits?  angle  anybits?  arg  bit_length  ceil  chr  coerce  denominator  digits  divmod  downto  eql?  equal?  even?  fdiv  finite?  floor  gcd  gcdlcm  infinite?  inspect  instance_of?  integer?  is_a?  kind_of?  lcm  magnitude  modulo  nan?  negative?  next  nobits?  nonzero?  numerator  object_id  odd?  ord  phase  positive?  pow  pred  quo  rationalize  remainder  round  size  succ  times  to_f  to_i  to_int  to_r  to_s  truncate  upto  zero?  |  ~
>>
$ opal-repl -Rchrome
>> require 'native'
=> true
>> $$[:navigator][:userAgent]
=> "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/95.0.4638.54 Safari/537.36"
>>

New runners

Runners are how you can use different environments to run Opal code on, using our opal command line tool. We added support for 3 new runners:

  • gjs - the GNOME JavaScript engine based on Gecko's JS engine
  • quickjs - an independently developed JavaScript engine with a good embedding support: https://bellard.org/quickjs/
  • miniracer - Ruby bindings to pure V8 (used to be supported only for repl)
$ opal -ropal/platform -Rchrome -e 'p OPAL_PLATFORM'
"headless-chrome"
$ opal -ropal/platform -Rnodejs -e 'p OPAL_PLATFORM'
"nodejs"
$ opal -ropal/platform -Rgjs -e 'p OPAL_PLATFORM'
"gjs"
$ opal -ropal/platform -Rquickjs -e 'p OPAL_PLATFORM'
"quickjs"
$ opal -ropal/platform -Rminiracer -e 'p OPAL_PLATFORM'
"opal-miniracer"

While the support for those is preliminary and works only with stdio, there is a possibility to extend those implementations when needed (patches welcome!). GJS is now one of the primary GNOME development platforms, powering software like Flatseal, allowing you to bridge all GNOME libraries, like Glib or GTK4 (making it a fully featured Node.js alternative, though much more low level and a lot less popular - but that's a digression).

Source map debugger

We have improved the source map support in this release. It should now much more accurately tell you which line/column is being executed. Which means - easier debugging! And that's not all, if source mapping goes wrong, you can always use our new source map debugging tool:

$ opal --debug-source-map -e '[1,2,3,4].each { |i| p i }'
https://sokra.github.io/source-map-visualization/#base64,T3BhbC5xdWV1ZShmdW5jdGlvbihPcGFsKSB7LyogR2VuZXJhdGVkIGJ5IE9wYWwgMS4zLjAgKi8KICB2YXIgJCQxLCBzZWxmID0gT3BhbC50b3AsICRuZXN0aW5nID0gW10sIG5pbCA9IE9wYWwubmlsLCAkJCQgPSBPcGFsLiQkJCwgJCQgPSBPcGFsLiQkLCAkc2VuZCA9IE9wYWwuc2VuZDsKCiAgT3BhbC5hZGRfc3R1YnMoWyckZWFjaCcsICckcCddKTsKICByZXR1cm4gJHNlbmQoWzEsIDIsIDMsIDRdLCAnZWFjaCcsIFtdLCAoJCQxID0gZnVuY3Rpb24oaSl7dmFyIHNlbGYgPSAkJDEuJCRzID09IG51bGwgPyB0aGlzIDogJCQxLiQkczsKCiAgICAKICAgIAogICAgaWYgKGkgPT0gbnVsbCkgewogICAgICBpID0gbmlsOwogICAgfTsKICAgIHJldHVybiBzZWxmLiRwKGkpO30sICQkMS4kJHMgPSBzZWxmLCAkJDEuJCRhcml0eSA9IDEsICQkMSkpCn0pOwo=,eyJ2ZXJzaW9uIjozLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyItZSJdLCJzb3VyY2VzQ29udGVudCI6WyJbMSwyLDMsNF0uZWFjaCB7IHxpfCBwIGkgfSJdLCJuYW1lcyI6WyI8bWFpbj4iLCJlYWNoIiwiMSIsIjIiLCIzIiwiNCIsImJsb2NrIGluIDxtYWluPiIsImkiLCJibG9jayAoMiBsZXZlbHMpIGluIDxtYWluPiIsInNlbGYiLCJwIl0sIm1hcHBpbmdzIjoiQUFBQUEsMkJBQUFBO0VBQUFBOztFQUFBQTtFQUFBQSxPQUFTQyxNQUFULENBQUNDLENBQUQsRUFBR0MsQ0FBSCxFQUFLQyxDQUFMLEVBQU9DLENBQVAsQ0FBU0osUUFBQUEsRUFBQUEsRUFBQUEsRUFBTUssZ0JBQUdDLENBQUhELEVBQUFFOzs7O0lBQUc7SUFBQTtJQUFBO0lBQUdBLE9BQUFDLElBQUFDLEdBQUFBLENBQUVILENBQUZHLEVBQU5KLGtCQUFBQSxpQkFBQUEsS0FBTkw7QUFBVEQ7In0=,WzEsMiwzLDRdLmVhY2ggeyB8aXwgcCBpIH0=

This utility can be also used to help you understand which Ruby code corresponds to which JavaScript output, for educational purposes.

Cache for Opal::Builder

Sprockets doesn't use Opal::Builder - it has its own cache, though it may need to be configured.

Upgrade to Opal 1.3 and forget the long compilation times when using opal command line tool or Opal::Builder thru any other means (like Opal::SimpleServer). This is implemented by caching the marshaled Compiler objects.

4x improvement for a simple script:

$ time opal _1.2.0_ -e 'p 123'
123

real    0m2.321s
user    0m2.213s
sys    0m0.107s
$ time opal _1.3.0_ -e 'p 123'
123

real    0m0.537s
user    0m0.450s
sys    0m0.090s

8x when including more core, e.g. opal-parser:

$ time opal _1.2.0_ -ropal-parser -e 'p eval("(0..1)")'
Object freezing is not supported by Opal
0..1

real    0m12.555s
user    0m12.250s
sys    0m0.349s
$ time opal _1.3.0_ -ropal-parser -e 'p eval("(0..1)")'
Object freezing is not supported by Opal
0..1

real    0m1.512s
user    0m1.405s
sys    0m0.249s

Async/await support

This big feature is experimental, and may totally change in future releases of Opal.

This complements the PromiseV2 feature introduced in Opal v1.2 and uses magic comments and a supported library (aptly named async) to await on JavaScript native promises.

Check it out:

# await: sleep
require "await"

puts "Let's wait 2 seconds..."
sleep 2
puts "Done!"

Please consult documentation of this feature: https://opalrb.com/docs/guides/v1.3.0/async

We are leaving this as experimental, because, as an Opal specific feature. Although it's not supported by Ruby itself the syntax is fully compatible, maybe some unexpected breakthrough will happen, with a port to a gem or to the language itself.

Conclusion

Are you starting to make a new web application and don't want to be forced to write JavaScript on the frontend?

If you are adventurous, why not try Opal? You may be surprised how ready it is for your usecase! :D


Resources