Opal Won the Fukuoka Ruby Award 2023 for Outstanding Performance

Opal has recently won the prestigious Fukuoka Ruby Award for Outstanding Performance in the 2023 edition. This is a major achievement for the project and its developers, and it reflects the significant contributions that Opal has made to the Ruby programming language and its ecosystem.

Opal is an open-source project that allows developers to write Ruby code and run it in the browser. It achieves this by compiling Ruby code into JavaScript, which can then be executed in a browser environment. This approach has many advantages, including the ability to reuse existing Ruby code and the ability to write web applications entirely in Ruby, without needing to use JavaScript at all.

Opal has been around since 2011, and it has steadily gained popularity over the years. It is now widely used by Ruby developers who want to use Ruby on both the front-end and the back-end of their applications or who want to port their Ruby libraries to JavaScript.

continue reading…

Opal 1.7, Ruby 3.2, --watch

We're excited to announce the release of Opal v1.7! This release includes support for Ruby 3.2 and the ability to watch file changes with the --watch flag.

Ruby 3.2 Support

Opal v1.7 adds support for Ruby 3.2, the latest version of the Ruby programming language. This means that you can use most of the new features and improvements in Ruby 3.2 when writing your Opal code.

Watching File Changes with the --watch Flag

Opal v1.7 introduces the --watch flag, which allows you to automatically recompile your code whenever a file is changed. This is especially useful when working on larger projects, as it saves you the time and effort of manually recompiling your code every time you make a change.

To use the --watch flag, simply pass it as an argument when running the opal command. For example:

opal --output=main.js --watch -c main.rb

This will start the Opal compiler and watch for any changes to the main.rb file. When a change is detected, the compiler will automatically recompile the code.

We hope these new features will make it easier for you to write and debug your Opal code. As always, please report any issues or feedback on the Opal GitHub repository.

If you work on macOS be sure to install v1.7.1 that includes a fix for reading the most updated content of watched files.

New runners

Support was added for running code in Deno, Firefox, and Safari, if any of those is available on your box you can try them out by passing --runner with safari, firefox, or deno.

Example:

$ # Remember to enable "Allow Remote Automation" in Safari's Develop menu
$ opal:master ⤑ bin/opal --runner=safari -ropal/platform -e "puts 'Hello from Webkit, heir of Konqueror!'"                                                     ~/C/opal
Connecting to localhost:9444...
Connecting to localhost:9444...
Connecting to localhost:9444...
Hello from Webkit, heir of Konqueror!

Resources

Opal 1.6 and Opal-RSpec 1.0

We are happy to announce that Opal 1.6 and Opal-RSpec 1.0 are out!

Opal is a Ruby (3.1) to JavaScript (ES5) compiler allowing you to write frontend code in pure Ruby and have it translated to clean and efficient JavaScript. It also includes a highly compatible core library that includes all the methods you've come to love. All this with a robust support of source maps, so you can debug Ruby code, not JavaScript in your Web Console. It can be used both to create websites entirely in Ruby (using Opal-Browser, or ports of known JavaScript libraries) and to port existing Ruby libraries to JavaScript, to be ran both in Node.JS and in web browsers (and other JavaScript environments).

Opal-RSpec is an Opal port of RSpec, a well known Ruby testing tool, allowing you to easily test your Ruby applications in browser and in Node, just like you can do so with MRI.

This release of Opal is focused primarily on improving upon MRI compatibility. We now fully support freezing and also we now have a new robust and hackable control flow instruction handling implementation (think: break, next, return...). Also, Windows support has been improved and Opal gained a parallel compile support - significantly improving compile times on multicore systems. In addition, the await subsystem is no longer experimental.

Try it now on opalrb.com/try, or get started instantly with no further steps required (except for installing Ruby):

gem install opal
# Try either of the following examples:
opal -e 'puts "Hello world!"'
opal -ce 'puts "Hello world!"' > hello_world.js; node hello_world.js
opal -OcEe 'puts "Hello world!"'
# Or run it in a web browser:
bundle init
bundle add opal-browser puma
bundle add rack -v '< 3'
bundle exec opal -Rserver -qopal-browser -rbrowser -e '$document.write("Hello world!")'
continue reading…

Opal 1.5: binding.irb in a web browser

We are happy to announce that Opal 1.5 is out!

Opal is a Ruby (3.1) to JavaScript (ES5) compiler allowing you to write frontend code in pure Ruby and have it translated to clean and efficient JavaScript. It also includes a highly compatible core library that includes all the methods you've come to love.

This release is mostly focused on performance improvements. In fact, in our tests, we got a 2x performance bump for the Opal Ruby compiler running in V8! As always, we have some new features (binding.irb), some compatibility improvements and a bunch of bugfixes.

Try it now on opalrb.com/try.

continue reading…

Opal 1.4: Ruby 3.1, performance, and bundle size

We are proud to announce an immediate availability of Opal 1.4!

Opal is a Ruby to JavaScript (ES5) compiler allowing you to write frontend code in pure Ruby (and a lot more!).

This version of Opal is about compatibility with Ruby 3.1 and lots of internal improvement around performance, code size, and readability of the generated code. Opal 1.3 was released just about 2 months ago, but we really wanted to get 3.1-level compatibility out around the time Ruby 3.1 is released this Christmas.

As always, you don't need to run Opal with Ruby 3.1 to benefit from the 3.1-level features, Ruby 2.6 will do just fine :)

continue reading…

Optimizing Opal output for size

Opal doesn't output the smallest code possible - that's not our goal. We want to output readable ES5 code and we have tools: JS minifiers (Terser and Google Closure Compiler), and tree shaking utilities (opal-optimizer) to bring the code size down.

JavaScript and Ruby certainly have some different semantics. Some things work similarly (like open classes), but some others don't - and those that don't require some boilerplate code. That not only reduces performance, but also increases load times, both crucial for JavaScript code.

In this article we will particularly focus on Terser, since it's the most widely used tool for Opal post-processing. Can Terser find every nook and cranny and optimize the resulting code to the minimal JavaScript version possible? Unfortunately not. It doesn't have a knowledge about which statements can produce side effects and which don't. And so it only does the transformations that are semantically equivalent. But while compiling, we may know something more.

continue reading…

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.

continue reading…

Opal 1.2

After about 11 years of development we are proud to announce the Opal 1.2 release!

Opal is a Ruby to JavaScript compiler, Ruby runtime and an idiomatic Ruby API for interfacing Browser/Node/anything that uses JS.

Notable new features

Full Ruby 3.0 support! (almost)

We missed a blog post about Opal 1.1, which was released with a preliminary support for Ruby 3.0 features, but Opal 1.2 finalizes this support. Let me update you about them!

An interesting thing is that you can still run older versions of Ruby on the backend, but use the new Ruby 3.0 features when writing Opal code for your frontend.

continue reading…

WebAssembly and advanced regular expressions with Opal

(This is a guest-post from the people at Interscript, featuring an in-depth account of the work done around building a web-assebly bridge and compiling Onigmo for Opal)


At Ribose Inc we develop Interscript, an open source Ruby implementation of interoperable transliteration schemes from ALA-LC, BGN/PCGN, ICAO, ISO, UN (by UNGEGN) and many, many other script conversion system authorities. The goal of this project is to achieve interoperable transliteration schemes allowing quality comparisons.

We decided to port our software to JavaScript using Opal (the Ruby to JavaScript compiler), so it can be also used in web browsers and Node environments. The problem is - Opal translates Ruby regular expressions (upon which we rely quite heavily) to JavaScript almost verbatim. This made our ported codebase incompatible on principle, so we searched for a better solution.

Unfortunately, Regexp is basically something like a programming language that has more than a dozen of incompatible implementations - even across the web browsers. For instance, we need lookbehind assertions, but even if there is a new standard in ECMAScript which adds lookbehind assertions, Safari doesn't implement that.

Given all this context let's dive into how we ported the original Ruby Regexp engine to the browser!

continue reading…

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.

continue reading…

Opal-RSpec 0.5: Newer RSpec version, improved Rake task, better documentation

Opal-RSpec

If you're a Rubyist, you know about RSpec. It's the testing framework that most of us like. RSpec's code base also happens to use a lot of features of the Ruby language, which means getting it to work on Opal is a challenge, but to some extent, we're there! Even if you're writing code in ES5/ES6, you might enjoy using RSpec as an alternative to Jasmine. Opal's native JS functionality makes that fairly easy to do.

What's new?

There were 394 commits since opal-rspec 0.4.3 that covered a variety of areas:

  • Opal itself - 30+ pull requests went into Opal 0.9 to improve RSpec's stability on Opal. This obviously benefits anyone using opal, not just opal-rspec users.
  • RSpec specs - opal-rspec 0.5 now runs and passes 80%+ of RSpec's own specs. For the first time, limitations, including some present in prior opal-rspec versions, are documented.
  • New versions - Base RSpec version has been upgraded to 3.1 from the 3.0 beta (we know we're still behind, but read on) and the Rake task works with Phantom JS 1.9.8 and 2.0.
  • New features - Node runner support and improved Rake task configurability.

How do you get started?

First step is adding it to your Gemfile and bundle install.

gem 'opal-rspec'

Then you'll need to ensure, for the default config, you have at 1.9.8 of PhantomJS installed.

Then you can start writing specs!

Put this in spec/42_spec.rb

describe 42 do
  subject { 43 }

  it { is_expected.to eq 43 }
end

Then in your Rakefile:

require 'opal/rspec/rake_task'
Opal::RSpec::RakeTask.new(:default)

After running bundle exec rake, you'll see:

Running phantomjs /usr/local/bundle/gems/opal-rspec-0.5.0/vendor/spec_runner.js "http://localhost:9999/"
Object freezing is not supported by Opal

.

Finished in 0.013 seconds (files took 0.163 seconds to load)
1 example, 0 failures

Right off the bat, you can see at least a few things Opal doesn't like about RSpec's code base (the freeze warning), but as of opal-rspec 0.5, those are either limited to this warning message or other documented limitations.

Formatter support works as well.

After running SPEC_OPTS="--format json" bundle exec rake:

Object freezing is not supported by Opal

{"examples":[{"description":"should eq 43", "full_description":"42 should eq 43", "status":"passed", "file_path":"http://localhost", "line_number":9999, "run_time":0.005}], "summary":{"duration":0.013, "example_count":1, "failure_count":0, "pending_count":0}, "summary_line":"1 example, 0 failures"}

Asynchronous testing on Opal

Since opal has a promise implementation built in, opal-rspec has some support for asynchronous testing. By default, any subject, it block, before(:each), after(:each), or around hook that returns a promise will cause RSpec to wait for promise resolution before continuing. In the subject case, any subject resolution in your specs will be against the promise result, not the promise, which should DRY up your specs.

Example: Create spec/async_spec.rb with this content:

describe 'async' do
  subject do
    promise = Promise.new
    delay 1 do
      promise.resolve 42
    end
    promise
  end

  it { is_expected.to be_a Promise }
  it { is_expected.to eq 42 }
end

Result:

Running phantomjs /usr/local/bundle/gems/opal-rspec-0.5.0/vendor/spec_runner.js "http://localhost:9999/"
Object freezing is not supported by Opal

.F.

Failures:

  1) async should be a kind of Promise
     Failure/Error: Unable to find matching line from backtrace
     Exception::ExpectationNotMetError:
       expected 42 to be a kind of Promise
     # ExpectationNotMetError: expected 42 to be a kind of Promise
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:4294
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:50378
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:33927
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:33950
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:33708
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:53200
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:3000
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:52274
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:52348
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:1055
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:14805
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:52554
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:52573
     #     at http://localhost:9999/assets/opal/rspec/sprockets_runner.js:52555
     #
     #   Showing full backtrace because every line was filtered out.
     #   See docs for RSpec::Configuration#backtrace_exclusion_patterns and
     #   RSpec::Configuration#backtrace_inclusion_patterns for more information.

Finished in 2.03 seconds (files took 0.156 seconds to load)
3 examples, 1 failure

Failed examples:

rspec http://localhost:9999 # async should be a kind of Promise

The failure is because we get a number back, not a Promise.

Other new tools in the ecosystem

JUnit & TeamCity/Rubymine formatter support

The chances are your CI tool supports at least 1 of those. Check out opal-rspec-formatter

Karma support

If you want to tap into the browser runners that Karma supports but keep using Opal, you might want to check out karma-opal-rspec.

Work to be done

As was mentioned above, we're still using RSpec 3.1, which as of this writing, is almost 1.5 years old. Due to the number of changes necessary to make RSpec Opal friendly, we cannot track with RSpec releases as fast as we would like. That said, several things we're doing should allow us to move faster in the future, including:

  • Constantly improving Opal code base that allows more Ruby features to work out of the box.
  • RSpec specs which helped improve Opal and allow us to easily identify far corners of RSpec's functionality.
  • Work on arity checking is already in-progress (opal-rspec currently doesn't run with arity checking enabled). This will tease out even more issues.

All of these will eventually lead to less monkey patching and more out of the box stuff that works.

How to help

Arity checking is the top priority right now. If you can assist with issues like this opal issue that are documented on the opal-rspec arity check issue, that will help move things along.

After that is complete, we can begin work on RSpec 3.4.

Opal 0.10: Rack 2 compatibility, improved keyword argument support, better source maps, and a whole lot more

Opal is now officially on the 0.10.x release path. Thanks to all the contributors to Opal who've worked hard to make this release possible. Some highlights from the latest release:

  • Improvements to source maps
  • Updates to methods in Array, Enumerable, and Module for RubySpec compliance
  • Rack v2 compatibility
  • Support for keyword arguments as lambda parameters, as well as keyword splats
  • Some IO enhancements to better work with Node.js
  • Marshalling support continue reading…

Opal 0.9: direct JS calls, console.rb, numeric updates, better reflection, and fixes aplenty

Opal is now officially on the 0.9.x release path, having just released 0.9.2. Thanks to all the contributors to Opal, especially over the holidays, and we're excited for what the new year will bring to the Opal community. Some highlights from the latest release:

Direct calling of JavaScript methods

You can now make direct JavaScript method calls on native JS objects using the recv.JS.method syntax. Has support for method calls, final callback (as a block), property getter and setter (via #[] and #[]=), splats, JavaScript keywords (via the ::JS module) and global functions (after require "js").

continue reading…

Opal 0.7.0: require, kwargs, docs, testing, lots of fixes

It's been almost a year from our 0.6.0 release and has been an awesome time for the Opal community. Today I'm proud to announce we have released v0.7.0, which comes packed with lots of good stuff and uncountable bug fixes.

#require #require_relative and #require_tree

The require system has been completely overhauled in Opal 0.7. The previous version was a rather smart wrapper around sprockets directives but had some limitations, especially when it came to interleaving require calls and code. Some gems couldn't be compiled with Opal just for that reason.

The new require system now relies on a module repository where each "module" actually corresponds to a Ruby file compiled with Opal. This means that #require calls aren't no-ops anymore.

In addition to that #require_relative support has been added and for feature parity with sprockets directives we're also introducing #require_tree. The latter will be particularly useful to require templates.

Keyword Arguments

This has been a super requested feature, and thanks to Adam Beynon they're now a reality. They still have some rough edges (as they did in their first CRuby/MRI incarnation) but the core is there for you all to enjoy.

continue reading…

Promises: Handling asynchronous code in Opal

When using Opal, one large omission from the stdlib are Threads. This is because javascript does not have threads, which makes asyncronous programming difficult in ruby. As javascript has increased in popularity with DOM libraries and web frameworks, callback hell was the standard way to handle asynchronous events in javascript. This was also the way events were handled in Opal applications. Until now.

What is so great about promises?

When looking at a simple example, the benefits of promises may not be obvious:

# old callback method
HTTP.get("url") do |response|
  puts "got response"
end

# using promises
HTTP.get("url").then do |response|
  puts "got response"
end

Initially the method call using a Promise looks just a more verbose version of the standard callback approach. The benefit of promises come through when promises are chained together. The result of the #then call actually returns a new Promise instance, which will not be resolved until the result of the first block resolves itself.

Callback hell

Lets take a slightly more complex example where an initial HTTP request is made for some user details, and then a second request is made using the result of the first json response:

continue reading…