Javalin: Template "hot reload"

Created on 10 Dec 2018  路  5Comments  路  Source: tipsy/javalin

I'm testing a dummy template as follows:

.get("/test", ctx -> ctx.render("templates/test.mustache", new HashMap<>()));

Editing the template (test.mustache) requires restarting the app (or maybe rebuilding the app, but stop + run in Eclipse anyway).

It's a major use case to be able to work with a template without a need of restarting the Javalin app for every change. I even tried to add .enableStaticFiles("src/resources/templates", Location.EXTERNAL), but no luck.

Is this a feature? Works differently in SparkJava by the way.

Thanks!

INFO QUESTION

Most helpful comment

Happy you found a solution @valtterip!

Gotta be done for every request, so obviously for development purposes only, but I'm fine with this now.

Instead of doing it "manually" per request, you could register a new FileRenderer:

JavalinRenderer.register((filePath, model) -> {
    MustacheFactory mustacheFactory = new DefaultMustacheFactory("./");
    StringWriter stringWriter = new StringWriter();
    mustacheFactory.compile(filePath).execute(stringWriter, model).close();
    return stringWriter.toString();
 )}, ".mustache")

Then you could use Javalin like normal, with calls to ctx.render(...).

Some sort of reload-on-modify -option would be cool

I don't think anything template-specific should be included in Javalin, other than a minimal example implementation that can be overridden by the user.

All 5 comments

You probably have to configure mustache to load templates from the file system rather than the classpath. I don't know how it is configured in Spark, but you could just look at the implementation there and copy the relevant parts.

Template files are not static files, so they're not effected by Loaction.EXTERNAL.

Edit: Accidentally closed the issue (mobile).

Found a tip from here https://github.com/spullara/mustache.java/issues/57

MustacheFactory mf = new DefaultMustacheFactory(templates);
mf.compile(the_template);
JavalinMustache.configure(mf);

Gotta be done for every request, so obviously for development purposes only, but I'm fine with this now. Some sort of reload-on-modify -option would be cool, it was so super easy to take the templates in use.

Happy you found a solution @valtterip!

Gotta be done for every request, so obviously for development purposes only, but I'm fine with this now.

Instead of doing it "manually" per request, you could register a new FileRenderer:

JavalinRenderer.register((filePath, model) -> {
    MustacheFactory mustacheFactory = new DefaultMustacheFactory("./");
    StringWriter stringWriter = new StringWriter();
    mustacheFactory.compile(filePath).execute(stringWriter, model).close();
    return stringWriter.toString();
 )}, ".mustache")

Then you could use Javalin like normal, with calls to ctx.render(...).

Some sort of reload-on-modify -option would be cool

I don't think anything template-specific should be included in Javalin, other than a minimal example implementation that can be overridden by the user.

My solution is to use WatchService to reload the MustacheFactory. This is to be used only during development:

final Path root = FileSystems.getDefault().getPath("src/main/resources");
WatchService watchService = FileSystems.getDefault().newWatchService();

// register for all subfolders
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        logger.info("Watching folder " + dir);
        dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        return FileVisitResult.CONTINUE;
    }
});

new Thread(() -> {
    try {
        boolean valid = true;
        while (valid) {
            boolean reloadMustache = false;

            WatchKey wk = watchService.take();
            for (WatchEvent<?> event : wk.pollEvents()) {
                WatchEvent.Kind<?> kind = event.kind();
                if (kind.name().equals("OVERFLOW")) {
                    continue;
                }

                Path changed = (Path) event.context();
                logger.info("File changed: " + changed);

                // add any other system that needs reloading here
                reloadMustache = reloadMustache || changed.toString().endsWith((".mustache"));
            }

            // reload assets as needed
            if (reloadMustache) {
                logger.info("Reloading all Mustache views");
                JavalinMustache.configure(new DefaultMustacheFactory(new File("src/main/resources")));

            }

            // reset the key
            valid = wk.reset();
            if (!valid) {
                logger.warn("Key has been unregistered! Exiting.");
            }
        }
    } catch (InterruptedException e) {
        logger.warn("Interrupted. Exiting.");
    }
    logger.info("Stopped monitoring src/main/resources");
}).start();

I don't think this belongs in Javalin, tbh. Hot reloading is something that can affect multiple aspects of the application, and is best left for an external system.

I don't think this belongs in Javalin, tbh.

I agree, but thank you for documenting your solution here. I've added the info tag.

Was this page helpful?
0 / 5 - 0 ratings