diff --git a/src/main/java/com/winterbe/react/React.java b/src/main/java/com/winterbe/react/React.java index 3c3f22b..bda7054 100644 --- a/src/main/java/com/winterbe/react/React.java +++ b/src/main/java/com/winterbe/react/React.java @@ -2,43 +2,58 @@ import jdk.nashorn.api.scripting.NashornScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; +import javax.script.*; +import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class React { - private ThreadLocal engineHolder = new ThreadLocal() { + private ThreadLocal scriptHolder = new ThreadLocal(){ @Override - protected NashornScriptEngine initialValue() { - NashornScriptEngine nashornScriptEngine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); + protected CompiledScript initialValue() { + + String script = Stream.of("static/nashorn-polyfill.js", + "static/vendor/react.js", + "static/vendor/showdown.min.js", + "static/commentBox.js") + .map(ThrowingFunction.wrap(React::slurp)).collect(Collectors.joining()); + try { - nashornScriptEngine.eval(read("static/nashorn-polyfill.js")); - nashornScriptEngine.eval(read("static/vendor/react.js")); - nashornScriptEngine.eval(read("static/vendor/showdown.min.js")); - nashornScriptEngine.eval(read("static/commentBox.js")); + ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); + Compilable compilingEngine = (Compilable) engine; + CompiledScript cs = compilingEngine.compile(script); + + // this is a noop but required to initialize the context of the compiled script + cs.eval(engine.getBindings(ScriptContext.ENGINE_SCOPE)); + return cs; } catch (ScriptException e) { throw new RuntimeException(e); } - return nashornScriptEngine; } }; public String renderCommentBox(List comments) { try { - Object html = engineHolder.get().invokeFunction("renderServer", comments); - return String.valueOf(html); + CompiledScript cscript = scriptHolder.get(); + + Invocable invocable = (Invocable) cscript.getEngine(); + + return String.valueOf(invocable.invokeFunction("renderServer", comments)); } catch (Exception e) { throw new IllegalStateException("failed to render react component", e); } } - private Reader read(String path) { - InputStream in = getClass().getClassLoader().getResourceAsStream(path); - return new InputStreamReader(in); + public static String slurp(String path) throws java.io.IOException { + try (InputStream resource = React.class.getClassLoader().getResourceAsStream(path)) { + return String.join("\n", new BufferedReader(new InputStreamReader(resource, + StandardCharsets.UTF_8)).lines().collect(Collectors.toList())) + "\n"; + } } } \ No newline at end of file diff --git a/src/main/java/com/winterbe/react/ThrowingFunction.java b/src/main/java/com/winterbe/react/ThrowingFunction.java new file mode 100644 index 0000000..348455e --- /dev/null +++ b/src/main/java/com/winterbe/react/ThrowingFunction.java @@ -0,0 +1,30 @@ +package com.winterbe.react; +import java.util.function.Function; + +/** + * Checked exceptions side effects which make functional programming hard. + * Here we convert checked to unchecked which is something Spring has done for years to compose + * interfaces here we are doing it to be able to properly compose functions. + * + * https://stackoverflow.com/a/29691649/329496 + * @param + * @param + */ +@FunctionalInterface +public interface ThrowingFunction extends Function { + + @Override + public default R apply(T t) { + try { + return throwingApply(t); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Function wrap(ThrowingFunction f) { + return f; + } + + R throwingApply(T t) throws Exception; +} \ No newline at end of file diff --git a/src/test/java/com/winterbe/react/ReactTest.java b/src/test/java/com/winterbe/react/ReactTest.java index 50a235f..8dcc0cb 100644 --- a/src/test/java/com/winterbe/react/ReactTest.java +++ b/src/test/java/com/winterbe/react/ReactTest.java @@ -5,7 +5,11 @@ import static org.junit.Assert.assertThat; import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -15,6 +19,7 @@ public class ReactTest { @Test public void testRenderCommentBox() throws Exception { + List comments = new ArrayList<>(); comments.add(new Comment("Peter Parker", "This is a comment.")); comments.add(new Comment("John Doe", "This is *another* comment."));