CommonJS for JVM over JSR-223

Langeress
3 min readDec 18, 2020
Java not versus JavaScript

Introduction to scripting API

Scripting for the Java Platform (JSR-223) is a framework specification for embedding scripts in Java source code. This is a very powerful tool that allows you to integrate scripting languages into the application such as: Ruby, Groovy, Scala, Python, JavaScript (All list of JVM languages).

Let’s consider the known implementations for JavaScript:

  • Nashorn is a default JavaScript engine built into Java.
  • Rhino is a JavaScript engine managed by the Mozilla Foundation.
  • Graal.js is a high performance implementation of the JavaScript engine, built on the GraalVM by Oracle Labs.

Integration of one separate script will look simple enough:

However, JavaScript, like many other languages, has support for a module ecosystem. The JavaScript modules implementation is CommonJS. Therefore, this code will not work:

To integrate a JavaScript module (such as a library for Node.js) we need a CommonJS implementation in Java.

CommonJS Modules Support for Nashorn, Rhino and Graal.js

Library jsr223-commonjs-modules adds support for CommonJS modules (aka require) inside a Nashorn, Rhino and Graal.js script engines. It is based on the specification for NodeJS modules and it supports loading modules from the node_modules folder just as Node does. Of course, it doesn't provide an implementation for Node's APIs, so any module that depends on those won't work.

Supported features:

  • Ready for use in scripting engines Nashorn, Rhino and Graal.js.
  • Displays the file name and line number in the error stacktrace.
  • Compatible with JSR-223 standard.
  • Implementation on pure Java.
  • No dependency on third-party libraries.

Getting the library using Maven

Add this dependency to your pom.xml to reference the library:

Usage

Enabling require in Nashorn script engine:

Enabling require in Rhino script engine:

Enabling require in Graal.js script engine:

This will expose a new global require function at the engine scope. Any code that is then run using this engine can make use of require.

The second argument specifies the root Folder from which modules are made available. Folder is an interface exposing a few calls that need to be implemented by backing providers to enable loading files and accessing subfolders. Out-of-the-box, the library supports loading modules from the filesystem and from Java resources.

Loading modules from the filesystem

Use the FilesystemFolder.create method to create an implementation of Folder rooted at a particular location in the filesystem:

You need to specify the encoding of the files. Most of the time UTF-8 will be a reasonable choice.

The resulting folder is rooted at the path you specified, and JavaScript code won’t be able to “escape” that root by using ../../... In other words, it behaves as is the root folder was the root of the filesystem.

Loading modules from Java resources

Use the ResourceFolder.create method to create an implementation of Folder backed by Java resources:

As for ResourceFolder, you need to specify the encoding for the files that are read.

More usage examples

See in my GitHub repository https://github.com/a-langer/jsr223-commonjs-modules.

--

--