diff --git a/doubleview/src/main/java/io/emeraldpay/doubleview/DoubleViewRendererConfiguration.java b/doubleview/src/main/java/io/emeraldpay/doubleview/DoubleViewRendererConfiguration.java index dd37409..f796c45 100644 --- a/doubleview/src/main/java/io/emeraldpay/doubleview/DoubleViewRendererConfiguration.java +++ b/doubleview/src/main/java/io/emeraldpay/doubleview/DoubleViewRendererConfiguration.java @@ -41,6 +41,12 @@ public class DoubleViewRendererConfiguration { */ private boolean useOptimization = true; + /** + * Enable development mode. + * In development mode, the renderer loads the code each time before rendering. Which allows to see the changes without restarting the application. + */ + private boolean devMode = false; + public String getClientBundleURL() { return clientBundleURL; } @@ -96,4 +102,17 @@ public String getHtmlTemplatePath() { public void setHtmlTemplatePath(String htmlTemplatePath) { this.htmlTemplatePath = htmlTemplatePath; } + + public boolean isDevMode() { + return devMode; + } + + public void setDevMode(boolean devMode) { + this.devMode = devMode; + } + + public void setDevMode() { + setDevMode(true); + } + } diff --git a/doubleview/src/main/java/io/emeraldpay/doubleview/JSContext.java b/doubleview/src/main/java/io/emeraldpay/doubleview/JSContext.java index b4f8114..a13719a 100644 --- a/doubleview/src/main/java/io/emeraldpay/doubleview/JSContext.java +++ b/doubleview/src/main/java/io/emeraldpay/doubleview/JSContext.java @@ -57,12 +57,17 @@ public Value getComponents() { return components; } - public static class Builder { - private final Source bundle; - private final Source render; - private final Context.Builder polyglotContext; + public static Builder builder(DoubleViewRendererConfiguration configuration) throws IOException { + if (configuration.isDevMode()) { + return new DevBuilder(configuration); + } + return new DefaultBuilder(configuration); + } + + public static abstract class Builder { + protected final Context.Builder polyglotContext; - Builder(DoubleViewRendererConfiguration configuration) throws IOException { + public Builder(DoubleViewRendererConfiguration configuration) { Engine.Builder engine = Engine.newBuilder("js") .out(System.out) .err(System.err); @@ -72,9 +77,6 @@ public static class Builder { engine = engine.option("engine.WarnInterpreterOnly", "false"); } - bundle = loadSource(configuration.getServerBundlePath()); - render = loadSource(configuration.getRendererScript()); - polyglotContext = Context.newBuilder("js") .engine(engine.build()) .allowAllAccess(true) @@ -82,7 +84,13 @@ public static class Builder { .option("js.unhandled-rejections", "throw"); } - public Source loadSource(String path) throws IOException { + /** + * Creates a fresh state for execution. Should be called each time the code is executed. + * @return a new JSContext + */ + abstract JSContext build(); + + public static Source loadSource(String path) throws IOException { if (path.startsWith(".") || path.startsWith("/")) { path = "file:" + path; } @@ -93,7 +101,7 @@ public Source loadSource(String path) throws IOException { if (path.startsWith("classpath:")) { String resourcePath = path.substring("classpath:".length()); fileName = resourcePath.substring(resourcePath.lastIndexOf('/') + 1); - try (var stream = getClass().getResourceAsStream(resourcePath)) { + try (var stream = Builder.class.getResourceAsStream(resourcePath)) { if (stream == null) { throw new IllegalArgumentException("Resource not found: " + resourcePath); } @@ -111,14 +119,51 @@ public Source loadSource(String path) throws IOException { .mimeType("application/javascript+module") .build(); } + } + + public static class DefaultBuilder extends Builder { + private final Source bundle; + private final Source render; + + private DefaultBuilder(DoubleViewRendererConfiguration configuration) throws IOException { + super(configuration); + bundle = loadSource(configuration.getServerBundlePath()); + render = loadSource(configuration.getRendererScript()); + + } - /** - * Creates a fresh state for execution. Should be called each time the code is executed. - * @return a new JSContext - */ public JSContext build() { return new JSContext(polyglotContext.build(), bundle, render); } } + + /** + * In development mode, the renderer loads the code each time before rendering. + * Which allows to see the changes without restarting the application. + */ + public static class DevBuilder extends Builder { + private final String serverBundlePath; + private final String rendererScript; + + private DevBuilder(DoubleViewRendererConfiguration configuration) { + super(configuration); + serverBundlePath = configuration.getServerBundlePath(); + rendererScript = configuration.getRendererScript(); + } + + public JSContext build() { + Source bundle; + Source render; + try { + bundle = loadSource(serverBundlePath); + render = loadSource(rendererScript); + } catch (IOException e) { + throw new RuntimeException("Server bundle or render script load failed in dev mode", e); + } + return new JSContext(polyglotContext.build(), bundle, render); + } + + } + } diff --git a/doubleview/src/main/java/io/emeraldpay/doubleview/JSContextProvider.java b/doubleview/src/main/java/io/emeraldpay/doubleview/JSContextProvider.java index bf429bf..affb6e1 100644 --- a/doubleview/src/main/java/io/emeraldpay/doubleview/JSContextProvider.java +++ b/doubleview/src/main/java/io/emeraldpay/doubleview/JSContextProvider.java @@ -10,7 +10,7 @@ public class JSContextProvider { public JSContextProvider(DoubleViewRendererConfiguration configuration) { context = ThreadLocal.withInitial(() -> { try { - return new JSContext.Builder(configuration); + return JSContext.builder(configuration); } catch (Exception e) { throw new RuntimeException("Failed to create JSContext", e); }