diff --git a/handlebars-maven-plugin/pom.xml b/handlebars-maven-plugin/pom.xml
index 2e6693e8..6933104e 100644
--- a/handlebars-maven-plugin/pom.xml
+++ b/handlebars-maven-plugin/pom.xml
@@ -21,6 +21,12 @@
${project.version}
+
+ org.openjdk.nashorn
+ nashorn-core
+ 15.4
+
+
ch.qos.logback
logback-classic
diff --git a/handlebars/pom.xml b/handlebars/pom.xml
index 581a14e8..f194c83a 100644
--- a/handlebars/pom.xml
+++ b/handlebars/pom.xml
@@ -170,6 +170,23 @@
org.openjdk.nashorn
nashorn-core
${nashorn.version}
+ true
+
+
+
+ org.graalvm.js
+ js-scriptengine
+ ${graaljs.version}
+ true
+
+
+
+ org.graalvm.polyglot
+ js
+ ${graaljs.version}
+ pom
+ runtime
+ true
@@ -240,6 +257,7 @@
15.4
+ 25.0.3
@@ -300,5 +318,55 @@
+
+
+ graaljs-jit
+
+
+ org.graalvm.compiler
+ compiler
+ ${graaljs.version}
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ graal-compiler-path
+ initialize
+
+ properties
+
+
+
+ copy-graaljs-modules
+ generate-test-resources
+
+ copy-dependencies
+
+
+ ${project.build.directory}/graaljs-modules
+ org.graalvm.polyglot,org.graalvm.js,org.graalvm.truffle,org.graalvm.sdk,org.graalvm.regex,org.graalvm.shadowed
+ compiler
+ pom
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ false
+ -Duser.language=en -Duser.country=US -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --module-path=${project.build.directory}/graaljs-modules --add-modules=org.graalvm.polyglot --upgrade-module-path=${org.graalvm.compiler:compiler:jar} --add-exports=org.graalvm.truffle.compiler/com.oracle.truffle.compiler=jdk.graal.compiler --add-exports=org.graalvm.truffle.compiler/com.oracle.truffle.compiler.hotspot=jdk.graal.compiler --add-exports=org.graalvm.truffle.compiler/com.oracle.truffle.compiler.hotspot.libgraal=jdk.graal.compiler --add-exports=org.graalvm.word/org.graalvm.word.impl=jdk.graal.compiler
+
+
+
+
+
diff --git a/handlebars/src/main/java/com/github/jknack/handlebars/Handlebars.java b/handlebars/src/main/java/com/github/jknack/handlebars/Handlebars.java
index fc257e2c..bfb20d00 100644
--- a/handlebars/src/main/java/com/github/jknack/handlebars/Handlebars.java
+++ b/handlebars/src/main/java/com/github/jknack/handlebars/Handlebars.java
@@ -27,7 +27,6 @@
import javax.script.Bindings;
import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
import org.slf4j.Logger;
@@ -38,6 +37,7 @@
import com.github.jknack.handlebars.internal.Files;
import com.github.jknack.handlebars.internal.FormatterChain;
import com.github.jknack.handlebars.internal.HbsParserFactory;
+import com.github.jknack.handlebars.internal.ScriptEngineFactory;
import com.github.jknack.handlebars.internal.Throwing;
import com.github.jknack.handlebars.io.ClassPathTemplateLoader;
import com.github.jknack.handlebars.io.CompositeTemplateLoader;
@@ -1442,19 +1442,21 @@ private static EscapingStrategy newEscapeChain(final EscapingStrategy[] chain) {
}
/**
- * @return Nashorn engine.
+ * @return A JavaScript engine (GraalJS or Nashorn, whichever is available).
*/
private ScriptEngine engine() {
synchronized (this) {
if (this.engine == null) {
- this.engine = new ScriptEngineManager().getEngineByName("nashorn");
+ this.engine = ScriptEngineFactory.create();
- Throwing.run(() -> {
- //polyfill globalThis as it is used in handlebars 4.7.9 and is not supported by nashorn
- engine.eval("var globalThis = this;");
- engine.eval(Files.read(this.handlebarsJsFile, charset));
- });
+ Throwing.run(
+ () -> {
+ if (ScriptEngineFactory.isNashorn(engine)) {
+ engine.eval("var globalThis = this;");
+ }
+ engine.eval(Files.read(this.handlebarsJsFile, charset));
+ });
}
return this.engine;
}
diff --git a/handlebars/src/main/java/com/github/jknack/handlebars/helper/DefaultHelperRegistry.java b/handlebars/src/main/java/com/github/jknack/handlebars/helper/DefaultHelperRegistry.java
index e8088787..66480d9d 100644
--- a/handlebars/src/main/java/com/github/jknack/handlebars/helper/DefaultHelperRegistry.java
+++ b/handlebars/src/main/java/com/github/jknack/handlebars/helper/DefaultHelperRegistry.java
@@ -27,7 +27,6 @@
import java.util.regex.Pattern;
import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,6 +36,7 @@
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.HelperRegistry;
import com.github.jknack.handlebars.internal.Files;
+import com.github.jknack.handlebars.internal.ScriptEngineFactory;
import com.github.jknack.handlebars.internal.Throwing;
/**
@@ -175,7 +175,9 @@ public HelperRegistry registerHelpers(final String filename, final String source
notNull(filename, "The filename is required.");
notEmpty(source, "The source is required.");
ScriptEngine engine = engine();
- Throwing.run(() -> engine.eval(adaptES6Literals(source)));
+ String adaptedSource =
+ ScriptEngineFactory.isNashorn(engine) ? adaptES6Literals(source) : source;
+ Throwing.run(() -> engine.eval(adaptedSource));
return this;
}
@@ -276,12 +278,12 @@ public DefaultHelperRegistry setCharset(final Charset charset) {
}
/**
- * @return Nashorn engine.
+ * @return A JavaScript engine (GraalJS or Nashorn, whichever is available).
*/
private ScriptEngine engine() {
synchronized (this) {
if (this.engine == null) {
- this.engine = new ScriptEngineManager().getEngineByName("nashorn");
+ this.engine = ScriptEngineFactory.create();
this.engine.put("Handlebars_java", this);
diff --git a/handlebars/src/main/java/com/github/jknack/handlebars/internal/ScriptEngineFactory.java b/handlebars/src/main/java/com/github/jknack/handlebars/internal/ScriptEngineFactory.java
new file mode 100644
index 00000000..5d37ecdb
--- /dev/null
+++ b/handlebars/src/main/java/com/github/jknack/handlebars/internal/ScriptEngineFactory.java
@@ -0,0 +1,101 @@
+/*
+ * Handlebars.java: https://github.com/jknack/handlebars.java
+ * Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.github.jknack.handlebars.internal;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+/**
+ * Factory for creating a JavaScript {@link ScriptEngine}. By default tries Nashorn first, then
+ * falls back to GraalJS. The engine can be forced via the {@code hbs.js_engine} system property.
+ */
+public final class ScriptEngineFactory {
+
+ private ScriptEngineFactory() {}
+
+ /**
+ * Create a JavaScript engine. If the system property {@code hbs.js_engine} is set, only that
+ * engine is tried. Otherwise tries Nashorn first, then GraalJS.
+ *
+ * @return a JavaScript ScriptEngine.
+ * @throws IllegalStateException if no JavaScript engine is available or the requested engine is
+ * not found.
+ */
+ public static ScriptEngine create() {
+ String requested = System.getProperty("hbs.js_engine");
+ if (requested != null) {
+ return createRequested(requested);
+ }
+ return createDefault();
+ }
+
+ private static ScriptEngine createDefault() {
+ ScriptEngineManager manager = new ScriptEngineManager();
+
+ ScriptEngine engine = manager.getEngineByName("nashorn");
+ if (engine != null) {
+ return engine;
+ }
+
+ engine = manager.getEngineByName("graal.js");
+ if (engine != null) {
+ configureGraalJS(engine);
+ return engine;
+ }
+
+ throw new IllegalStateException(
+ "No JavaScript engine found. Add either GraalJS or Nashorn to the classpath.");
+ }
+
+ private static ScriptEngine createRequested(String name) {
+ ScriptEngineManager manager = new ScriptEngineManager();
+
+ switch (name) {
+ case "nashorn":
+ ScriptEngine nashorn = manager.getEngineByName("nashorn");
+ if (nashorn == null) {
+ throw new IllegalStateException(
+ "JavaScript engine 'nashorn' requested via hbs.js_engine but is not available."
+ + " Add org.openjdk.nashorn:nashorn-core to the classpath.");
+ }
+ return nashorn;
+
+ case "graaljs":
+ ScriptEngine graaljs = manager.getEngineByName("graal.js");
+ if (graaljs == null) {
+ throw new IllegalStateException(
+ "JavaScript engine 'graaljs' requested via hbs.js_engine but is not available."
+ + " Add org.graalvm.js:js-scriptengine to the classpath.");
+ }
+ configureGraalJS(graaljs);
+ return graaljs;
+
+ default:
+ throw new IllegalStateException(
+ "Unknown hbs.js_engine value: '"
+ + name
+ + "'. Supported values are 'nashorn' and 'graaljs'.");
+ }
+ }
+
+ private static void configureGraalJS(ScriptEngine engine) {
+ Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
+ bindings.put("polyglot.js.allowAllAccess", true);
+ bindings.put("polyglot.js.nashorn-compat", true);
+ bindings.put("polyglot.js.ecmascript-version", "2022");
+ }
+
+ /**
+ * Check whether the given engine is Nashorn.
+ *
+ * @param engine the script engine.
+ * @return true if the engine is Nashorn.
+ */
+ public static boolean isNashorn(ScriptEngine engine) {
+ return engine.getFactory().getEngineName().toLowerCase().contains("nashorn");
+ }
+}
diff --git a/handlebars/src/test/java/com/github/jknack/handlebars/NashornTest.java b/handlebars/src/test/java/com/github/jknack/handlebars/NashornTest.java
index 218db605..2d9b8cc8 100644
--- a/handlebars/src/test/java/com/github/jknack/handlebars/NashornTest.java
+++ b/handlebars/src/test/java/com/github/jknack/handlebars/NashornTest.java
@@ -13,11 +13,11 @@
import java.util.Map;
import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
import javax.script.SimpleBindings;
import org.junit.jupiter.api.Test;
+import com.github.jknack.handlebars.internal.ScriptEngineFactory;
import com.github.jknack.handlebars.js.JavaScriptHelperTest;
public class NashornTest {
@@ -45,13 +45,13 @@ public String getName() {
@Test
public void bootstrap() throws Exception {
Handlebars hbs = new Handlebars();
- ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
+ ScriptEngine engine = ScriptEngineFactory.create();
SimpleBindings bindings = new SimpleBindings();
bindings.put("Handlebars_java", hbs);
- nashorn.eval(
+ engine.eval(
new FileReader(Paths.get("src/main/resources/helpers.nashorn.js").toFile()), bindings);
- nashorn.eval(
+ engine.eval(
new FileReader(
Paths.get("src/test/resources/com/github/jknack/handlebars/js/helpers.js").toFile()),
bindings);