rOpenSci | A Major Upgrade of the V8 package

A Major Upgrade of the V8 package

This week version 2.0 of the V8 package has been released to CRAN. Go get it now!

install.packages("V8")

The V8 package provides an embedded JavaScript engine that can be used inside of R. You can use it interactively as a JavaScript console, but it is mostly useful for wrapping JavaScript libraries in R packages. Some cool examples include jsonld, jsonvalidate, and daff.

This major and much anticipated upgrade brings a new version of the JavaScript engine, effectively upgrading the JavaScript language. This opens up a lot of new possibilities, but it can also introduce some subtle behavioral changes, so read on carefully.

🔗 A Brief History of V8

The V8 R package provides R bindings to Google’s V8 JavaScript engine: the core of both Google Chrome and NodeJS JavaScript interpreter. The amount of engineering that Google has invested in V8 to make Chrome faster and more reliable with every release is absolutely incredible. Their recent 10 year anniversary blog post highlights some of the major improvements from the last decade. Also below is a useless but cool visualization of the evolving codebase.

Ever since the initial 2014 release of the V8 package on CRAN, we used version 3.14 of Google’s V8 engine. This V8 version implemented ECMAscript version 5 (ES5), which is the official JavaScript language spec from 2009 (now sometimes referred to as “traditional JavaScript”).

Back in 2009, JavaScript was an odd but relatively simple scripting language. However over the past 10 years, both the V8 engine as well as the JavaScript language definition have completely evolved. In 2015, ES6 added significant new syntax for writing complex applications, introducing promises, array buffers, classes, modules, and much more. Since then, the ECMA standard is now revised annually, with more functionality continuously added to support modern internet. With the new latest release of the CRAN package this functionality now becomes available in R.

🔗 Upgrading the R bindings

Porting the R package to support recent V8 engines was not easy. The V8 maintainers have the bad habit of changing the API all the time, so the Linux distributions don’t like to ship and upgrade libv8. CRAN requires that R packages will work on Windows, MacOS, Fedora, Debian, and Solaris, and for a long time it seemed impossible to upgrade the R package while keeping it working on all systems.

Things changed a few weeks ago when Debian announced libv8 would be removed in the upcoming Debian Buster, and instead a new package libnode-dev would be added which contains the V8 build that is included with NodeJS 10.15. This will allow to us to install the R package on Debian 10 (Buster) and Ubuntu 19.04 (Disco) and take advantage of Node’s embedded V8 engine.

To complete the migration I also had to build new libv8 binaries for MacOS and Windows to ship with the CRAN binary packages. The V8 CRAN binary package will now ship with libv8 7.2 on MacOS and libv8 6.2 on Windows. The latter is a bit behind because I couldn’t get the most recent libv8 to build with the Rtools compilers, but both versions should support modern JavaScript features.

🔗 A Word of Caution

Currently the main benefit of upgrading V8 is that we can load JS libraries which require a recent JavaScript standard. Some JS libraries may automatically switch to async behavior when supported. This is usually a good thing, unless you were not anticipating this in your R wrapper, and you may get unexpected behavior after upgrading V8!

I got surprised by this myself in the jsonld package. It turned out most functions in the jsonld.js library return a promise instead of the actual results, but I never realized this because the old V8 did not support promises. To make things work with the new V8, I changed the R wrappers to store results via callback functions.

If you write R packages with JS bindings, note that some Linux systems will still be running the legacy 3.14 engine for now. Hence to release the package on CRAN, you still need to use a fallback polyfill or transpile your modern JS to traditional JS something like babel.js to support legacy ES5 engines.

🔗 Modern JavaScript in R

Modern JavaScript opens up a whole new set of possibilities and challenges. The current version of the R package does not change the R interface yet, but perhaps future versions will add special R wrappers for resolving promises and calling async JavaScript code from R.

I’m not sure yet what this would look like. It’s an interesting case because JavaScript is primarily an async (non-blocking) language, whereas R calls are usually blocking. Maybe we can use R promises to run non-blocking JS tasks in the background, but I have not come across a good use case yet.