Opal 1.0

Dear Opalists of the world,
the time has come, 1.0 has been released!

This is, of course, a really important milestone, one that I've been waiting on for about seven years. I started advocating for releasing version 1.0 back in 2012 when Opal was still at version 0.3. After all, at that time I already had code in production, and according to Semver (which by then was still a new thing) that's one of the criteria for releasing 1.0:

How do I know when to release 1.0.0?

If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you’re worrying a lot about backward compatibility, you should probably already be 1.0.0.

https://semver.org/#how-do-i-know-when-to-release-100

I was so proud and excited about this ability to use Ruby for frontend code! I was writing the logic for an in-page product filter and Ruby allowed me to solve the problem quickly, with its signature concise syntax, and leveraging the power of enumerable. That immensely reduced the lines of code I had to write and allowed me to concentrate on just the core of the problem.

Many years have passed, and I'm even more proud of the project that Opal has become, the maturity and the feature parity with MRI is astounding, the new features that are coming in version 1.0 are even more amazing, and the roadmap ahead makes Opal one of the best choices among compile-to-JS languages.


Before diving into each new feature I'd like to give immense credit for the MVP of this release, namely our good Ilia Bylich! Without his unrelenting work and deep understanding of Ruby, we wouldn't be where we are. Thanks man! πŸ‘πŸ‘πŸ‘

Notable New Features

Native Prototype Chain lookup for modules

Opal 1.0 comes with a complete overhaul of class and module hierarchy that now closely maps the original MRI design. In fact, the prototype chain of any object will now include an entry for every Ruby ancestor, leaving to the JavaScript engine the caching and optimization of methods. This change also made possible the implementation of Module.prepend (see below) and a way simpler and correct management of super calls.

Module.prepend

One of the biggest and much-anticipated additions is certainly Module.prepend. The feature has received a number of implementation attempts through the years, but before the new prototype architecture no clean design was possible.

To get the best results in terms of performance, the same rules apply for Opal and MRI: do all your include/prepend/extend as early as possible and never touch them again if you can.

Faster C Lexer

As you may already know Opal now relies on the parser gem to parse Ruby code (the same used by Rubocop). Parser is an incredibly complete piece of work and perfectly mimics the MRI parser with all its quirks. That means that it's equally heavy on Ruby code, making compiling noticeably slower. After some benchmarking, we identified the bottleneck in the lexing phase.

Luckily, if you run Opal on MRI you can now add the c_lexer gem to your Gemfile and save whole seconds while compiling your code! πŸš€

Usability: error messages & source maps

We packed a bunch of really nice stuff to make your day-to-day development experience even better:

  • full filenames in compiled code (previously only the basename was used)
  • stack traces that point to the error in the Opal code (instead of the location of the error inside the parser/compiler)
  • better use of displayName to clearly between Ruby and JavaScript in stack-traces within the browser
  • inline all-in-one source maps comment with improved precision, no more additional requests with complicated server setups and off-by-one line errors

We're loving it and we know you're gonna love it too! ❀️

Missing requires management

It's now possible to ignore missing/dynamic requires during compile-time and only raise errors at runtime, just like MRI would do.

Targeting Ruby 2.5

A bunch of features have been added that come right out of Ruby 2.4 and 2.5, those include:

  • Array#prepend
  • Array#append
  • Array#max
  • Array#min
  • Complex#finite?
  • Complex#infinite?
  • Complex#infinite?
  • Date#to_time
  • Date#next_year
  • Date#prev_year
  • Hash#slice
  • Hash#transform_keys
  • Hash#transform_keys!
  • Numeric#finite?
  • Numeric#infinite?
  • Numeric#infinite?
  • Integer#allbits?
  • Integer#anybits?
  • Integer#digits
  • Integer#nobits?
  • Integer#pow
  • Integer#remainder
  • Integer.sqrt
  • Random.urandom
  • String#delete_prefix
  • String#delete_suffix
  • String#casecmp?
  • Kernel#yield_self
  • String#unpack1
  • String#to_r
  • String#to_c
  • String#match?
  • String#unicode_normalize returns self
  • String#unicode_normalized? returns true

Random generator, Pack/Unpack, Nodejs FS support

A ton of other stuff has been added and we encourage you to have a peek at the commit list.

Opal now implements a Mersenne Twister pseudo-random number generator, that's the same algorithm employed by MRI.

An initial implementation of Array#pack and String#unpack has been added, if you don't know what those are you can learn about them here.

Thanks to the contributions of our good @Mogztter of Asciidoctor.js fame we got a bunch of additions to the Node.js support libraries that will make your life much easier if you plan to use the File and Dir classes with Opal from Node.js.

Roadmap

Finally let's spend a word on the future, laying out the roadmap for the next releases. Anyone is encouraged to weight in on our gitter chatroom and give feedback about this, but I feel pretty confident that we're setting the priorities right. 😎

Strict-mode & Webpack (v1.1)

Webpacker is going to be the default in Rails 6 and we're committed to making Webpack support for Opal a first-class citizen. Webpack also requires loaded modules to comply with strict-mode. We're already moving in that direction, thanks to the patient work of @janbiederman, and I'm pleased to tell that the early results are very promising and showing already noticeable performance improvements for most benchmarks! πŸš€

Dead Code Elimination (v1.2)

This is another big one, Dead Code Elimination is a technique used to remove from the compiled bundle all the code that it's never going to be used. The more we implement methods from Ruby's corelib and stdlib the more the compiled bundle will grow. We know, of course, that the resulting application code will often be much smaller, but we also need to acknowledge the fact that is likely that the vast majority of the code will never be run.

Opal is already able to track methods and constants usage while compiling a file, and the basic idea is to use that to identify methods, classes, and modules that will never be used once. If we can pull this off the compiled bundle size will shrink dramatically contemporarily improving boot time! πŸ—œ

Mutable Strings (v1.3)

Opal maps Ruby's String class directly to its JavaScript counterpart. That means that, in Opal, strings are immutable and we need to update or patch gems before using them on a JavaScript engine. Adding mutable strings (while still using frozen ones by default) will be of great help for compatibility, and for those situations where it's much easier to operate on strings using bang! methods. πŸ’₯

Ecosystem

We'll release updated versions of all the gems in the ecosystem in the following weeks updating their Gem spec requirements to include Opal 1.0 and patches as needed. That includes opal-rails, opal-sprockets, opal-jquery, opal-rspec, opal-haml, etc.

If you encounter any issue while upgrading or using your favorite gems with Opal 1.0 please let us know asking in the gitter channel, with the #opalrb tag on StackOverflow, or opening an issue on GitHub.

Conclusion

Opal is doing better than ever and it's a great time to start building awesome things with it, the kind that we usually build in the Ruby community, full of creativity, with blithe disregard of current programming dogma and open to everyone.

(Happy Mother's Day! 🌸)


Resources