MOTIVATION
Babel は最新の ECMAScript 構文 (クラスやラムダや const
, let
など) を古い仕様の JS 環境でも実行できるように変換するトランスコンパイラ。例えば React は ECMA2015 + JSX のソースを Babel で一般的なブラウザでも解釈できる JS に変換しています。
React を使う用事があって、使い慣れた Finagle か Akka で API サーバを用意しようと思い、Node/React と JavaVM はうまく共存させたかったのがそもそもの始まり。Babel でブラウザ実行可能な JS が生成できるなら Java Scripting API の Nashorn で ES2015 が使えたり Node.js のライブラリが利用できるのでは? と。
CONCLUSION
結論から先に。
ES2015 で書かれた JS を Babel でトランスコンパイルして Nashorn で実行することは可能。ただし:

厳しい。理由は以下の通り。
- babel-standalone の準備
eval()
に 30 秒かかる。うなる CPU ファン。まぁサーバサイドなどは準備の済んだScriptEngine
をキャッシュしておけば良いかもしれない。 - 準備時間を妥協できるならトランスコンパイルは可能。ただ Nashorn には
require()
がなく、従って外部ライブラリが使えない。webpack も併せて使う必要がありそう。あるいは Java で同等機能を実装して bindings で渡せば回避可能かもしれない。 - そもそも環境設定に
npm
を前提とするので Nashorn を使わずとも babel を直接起動する手段がある (そしてそっちの方が速い)。 - babel-standalone は presets に env が使えないなど制約が厳しい。
従って Nashorn を使うより実行環境の node
なり npm
なりを Runtime.exec()
で使用するほうが良い。
PLAN
こんなプランで実行しましたという手順。
npm で es2015 の presets をインストールしています (が、本当に必要だったかは不明)。
$ npm init
$ npm install babel-cli babel-preset-es2015 --save
1. babel.js の準備
babel-standalone の Installation にあるようにリリースページや npm で babel.js
を入手します。
final ScriptEngineManager manager = new ScriptEngineManager();
final ScriptEngine babel = manager.getEngineByName("JavaScript");
final String babelJS = "babel.js";
babel.put(ScriptEngine.FILENAME, babelJS);
try(Reader in = new FileReader(babelJS)){
babel.eval(in);
}
この babel.eval(in)
で 30 秒程度かかります。また babel.min.js
を使用すると例外が発生しますので babel.js
を使用します。
javax.script.ScriptException: SyntaxError: empty range in char class in babel.min.js at line number 4
at jdk.scripting.nashorn/jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(Unknown Source)
...
Caused by: babel.min.js:4 SyntaxError: empty range in char class
at jdk.scripting.nashorn/jdk.nashorn.internal.runtime.ECMAErrors.error(Unknown Source)
...
Caused by: jdk.nashorn.internal.runtime.ParserException: empty range in char class
at jdk.scripting.nashorn/jdk.nashorn.internal.runtime.regexp.RegExp.throwParserException(Unknown Source)
...
2. トランスコンパイルの実行
final String es2015JS = "es2015.js";
final String es2015 = new String(Files.readAllBytes(Paths.get(es2015JS)), StandardCharsets.UTF_8);
babel.put(ScriptEngine.FILENAME, "<transcompile>");
babel.put("src", es2015);
babel.put("a", new Object[3]);
final Object[] result = (Object[])babel.eval(
"var r = Babel.transform(src, {presets:['es2015']});\n" +
"a[0] = r.code;\n" +
"a[1] = r.map;\n" +
"a[2] = r.ast;\n" +
"a"
);
System.out.println(result[0]);
Babel.transformFileSync
は何故か undefined でした。関数内で arguments とか使っているから?
とりあえず一度 ES2015 のソースを読み込んで transform
し結果をリターンバッファに格納します。es2015.js の中は以下の通り:
// run `npm install kuromoji` before
import kuromoji from "kuromoji"
kuromoji.builder({ dicPath: "./node_modules/kuromoji/dict" }).build((err, tokenizer) => {
var path = tokenizer.tokenize("すもももももももものうち")
console.log(path)
})
Nashorn 上でのトランスコンパイルにより以下のようなソースが生成されました。
"use strict";
var _kuromoji = require("kuromoji");
var _kuromoji2 = _interopRequireDefault(_kuromoji);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_kuromoji2.default.builder({ dicPath: "./node_modules/kuromoji/dict" }).build(function (err, tokenizer) {
var path = tokenizer.tokenize("すもももももももものうち");
console.log(path);
}); // run `npm install kuromoji` before
Babel でトランスコンパイルした結果も全く同じ。
$ node_modules/.bin/babel --presets=es2015 es2015.js
3. トランスコンパイルされたコードの実行
スクリプトエンジンを Babel とは別にしてトランスコンパイルされたコードを実行します。
final ScriptEngine engine = manager.getEngineByName("JavaScript");
engine.put(ScriptEngine.FILENAME, es2015JS);
engine.eval(result[0].toString());
しかし require()
を使っているので実行できませんね
javax.script.ScriptException: ReferenceError: "require" is not defined in es2015.js at line number 3
at jdk.scripting.nashorn/jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(Unknown Source)
...
おしまし