Resolving modules using require.js and Java/Rhino
Asked Answered
M

1

8

I'm trying to get require.js to load modules on the server-side with Java 6 and Rhino.

I'm able to load require.js itself just fine. Rhino can see the require() function. I can tell because Rhino complains that it can't find the function when I change require() to something else like requireffdkj().

But when I try to require even a simple JS, like hello.js

var hello = 'hello';

using either of the following:

require('hello');
require('./hello');

it doesn't work. I get

Caused by: javax.script.ScriptException: sun.org.mozilla.javascript.internal.JavaScriptException: [object Error] (<Unknown source>#31) in <Unknown source> at line number 31
    at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:153)
    at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:167)
    at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)

I have my hello.js at the top of the Java classpath. That's where I have require.js as well. I tried moving hello.js everywhere I could think it might possibly go, including the root of my hard drive, the root of my user directory, the directory from which I'm running my Java app, etc. Nothing works.

I looked at the CommonJS spec (http://wiki.commonjs.org/wiki/Modules/1.0) and it says that top-level IDs (like hello) are resolved from the "conceptual module name space root", whereas relative IDs (like ./hello) are resolved against the calling module. I'm not sure where either of those baselines is, and I suspect that's the issue.

Any suggestions? Can I even use require.js from Rhino?

EDIT: Thinking that I need to set the environment up as per Pointy's suggestion in the comment below, I tried evaluating r.js as well. (I tried evaluating after evaluating require.js, and then again before require.js.) In either case I get an error:

Caused by: javax.script.ScriptException: sun.org.mozilla.javascript.internal.EcmaError: ReferenceError: "arguments" is not defined. (<Unknown source>#19) in <Unknown source> at line number 19
    at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:153)
    at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:167)
    at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)

"arguments" appears to be a variable in r.js. I think it's for command line arguments, so I don't think r.js is the right path for what I'm trying to do. Not sure though.

Mcbride answered 17/6, 2012 at 21:15 Comment(6)
Something's going to have to provide the APIs the require.js expects from the browser; that is, the library uses web browser facilities to load scripts. What are you doing to make those available?Prairial
Probably nothing. :-) What do I need? I saw that there's a compiler.jar if you want to use the Clojure compiler, but that's not me. Also I saw rhino.jar, but it looked like that's for Rhino itself, which Java 6 already includes.Mcbride
Well I'm not 100% sure what require.js does, but for example many such libraries fetch scripts by constructing new <script> tags in the browser. You might find it easier to simply write your own import service in Java and then provide bindings for your JavaScript environment; that's what I've always done (though never with Rhino APIs directly; I've always used the JSE ScriptEngine facilities).Prairial
Also keep in mind that if you write your own Java-based script import service, it will be synchronous, and thus it will remove a lot of the complexity that require.js has to deal with in the web browser environment.Prairial
Thanks Pointy. In this case I'm using RequireJS because it provides a CommonJS environment so I can use the PageDown library, which apparently wants a CommonJS environment on the server side. RequireJS supports both synchronous and asynchronous loads, and I believe the call above (pass a string into require()) instead of passing in an array) is the synchronous version.Mcbride
Well probably the simplest thing to do is to look through the require.js source code and figure out how it expects to be able to load scripts.Prairial
F
14

require.js works well with rhino. Recently, I used it in a project.

  1. You have to make sure to use r.js (not require.js) , modified version of require.js for rhino.
  2. You have to extend ScritableObject class to implement load and print function. When you call require(["a"]), the load function in this class will be called, you can tweak this function to load the js file from any location. In the below example, I load from classpath.
  3. You have to define the property arguments in the sharedscope as shown below in the sample code
  4. Optionally, you can configure the sub path using require.config, to specify the subdirectory inside classpath where js files are located.

JsRuntimeSupport

public class JsRuntimeSupport extends ScriptableObject {

    private static final long serialVersionUID = 1L;
    private static Logger logger = Logger.getLogger(JsRuntimeSupport.class);
    private static final boolean silent = false;

    @Override
    public String getClassName() {
        return "test";
    }

    public static void print(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
      if (silent)
        return;
        for (int i = 0; i < args.length; i++)
          logger.info(Context.toString(args[i]));
    }

    public static void load(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws FileNotFoundException, IOException {
        JsRuntimeSupport shell = (JsRuntimeSupport) getTopLevelScope(thisObj);
        for (int i = 0; i < args.length; i++) {
            logger.info("Loading file " + Context.toString(args[i]));
            shell.processSource(cx, Context.toString(args[i]));
        }
    }

    private void processSource(Context cx, String filename)
            throws FileNotFoundException, IOException {
        cx.evaluateReader(this, new InputStreamReader(getInputStream(filename)), filename, 1, null);
    }

    private InputStream getInputStream(String file) throws IOException {
        return new ClassPathResource(file).getInputStream();
    }
}

Sample Code

public class RJsDemo {

    @Test
    public void simpleRhinoTest() throws FileNotFoundException, IOException {
    Context cx = Context.enter();

    final JsRuntimeSupport browserSupport = new JsRuntimeSupport();

    final ScriptableObject sharedScope = cx.initStandardObjects(browserSupport, true);

    String[] names = { "print", "load" };
    sharedScope.defineFunctionProperties(names, sharedScope.getClass(), ScriptableObject.DONTENUM);

    Scriptable argsObj = cx.newArray(sharedScope, new Object[] {});
    sharedScope.defineProperty("arguments", argsObj, ScriptableObject.DONTENUM);

    cx.evaluateReader(sharedScope, new FileReader("./r.js"), "require", 1, null);
    cx.evaluateReader(sharedScope, new FileReader("./loader.js"), "loader", 1, null);

    Context.exit();

  }

}

loader.js

require.config({
    baseUrl: "js/app"
});

require (["a", "b"], function(a,  b) {
    print('modules loaded');
});

js/app directory should be in your classpath.

Faustinafaustine answered 17/6, 2012 at 23:42 Comment(6)
Thanks sperumal. I will check this out. Right now I am investigating another approach (Java 7, which has Rhino 1.7R3, which implements CommonJS modules, thus making require.js unnecessary for me). But I would prefer to be able to do this in Java 6 and your answer looks helpful. Will let you know.Mcbride
This works swimmingly on my PC, but fails on Android with a org.mozilla.javascript.EvaluatorExpression: Too deep recursion while parsing (r.js#1).Orchard
Managed to fix Android's too deep recursion issue by loading r.js inside a Thread with a larger stack size. Unfortunately, now when loader.js tries to require a simple module that looks like define({value: 42});, I get the following error: org.mozilla.javascript.EcmaError: ReferenceError: "define" is not defined. Blarg.Orchard
@Faustinafaustine You are awesome. But is there a way to use this together with envjs? I am failing to add support for both. It seems like one or the other :(Saloop
I should mention that I did set sealed to false when I tested as wellSaloop
Is that possible to load nodejs "FS" module to Rihino? #24557808Theron

© 2022 - 2024 — McMap. All rights reserved.