perwendel / spark

A simple expressive web framework for java. Spark has a kotlin DSL https://github.com/perwendel/spark-kotlin
Apache License 2.0
9.64k stars 1.56k forks source link

Auto reload (twig) templates? #1029

Closed mhulse closed 5 years ago

mhulse commented 6 years ago

Hello,

I'm using Twig for templating.

Is it possible to auto-reload template changes? I see there's an option for static files, but not seeing much talk about auto-reloading templates.

Thanks!

mhulse commented 6 years ago

I guess a better question is, do most people restart the dev server to see their template changes?

Is there a way for spark to auto-recompile? Is this a Gradle/Maven thing?

I ask because the docs mention how to reload assets (like CSS/JS/etc.)... As a mostly front-end guy (who is used to dealing with other languages like Python/PHP) I'd like to think templates could be updated on the fly.

tipsy commented 5 years ago

The best way to handle this is to set an external location for your templates and disable caching. That way the template engine will read and build the template for each request.

mhulse commented 5 years ago

Interesting! This, feature, would, be, so, useful .....

I use twig for templating. At some point I'll look at how assets are handled under-the-hood. It sounds like it's a similar/the same concept.

Thanks for the reply/help! 👍

tipsy commented 5 years ago

I don't know how twig works, but I can dig up the code for configuring Velocity.

tipsy commented 5 years ago

I don't know if any of this applies to Twig, but this is what I've done for Velocity in the past:

VelocityEngine().apply {
    if (localhost) {
        setProperty("file.resource.loader.path", localhostResourcePath)
        setProperty("velocimacro.library.autoreload", "true")
        setProperty("file.resource.loader.cache", "false")
        setProperty("velocimacro.permissions.allow.inline.to.replace.global", "true")
    } else {
        setProperty("resource.loader", "class")
        setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader")
    }
}

I use an external location for localhost, and classpath for the final jar.

Save template, refresh page, instant changes.

mhulse commented 5 years ago

I don't know if any of this applies to Twig, but this is what I've done for Velocity in the past:

Oooooh, awesome! Thank you for sharing!

I'll see if I can adapt to twig and will report back my results. Thanks for the help!

mhulse commented 5 years ago

As I'm researching my options here, I did stumble across this:

https://github.com/jtwig/jtwig (a page that lists plugins)

Which led me to this:

https://github.com/jtwig/jtwig-hot-reloading-extension

Looking into how I can modify this:

https://github.com/perwendel/spark-template-engines/tree/master/spark-template-jtwig

Using the above example code and tips the example from @tipsy ...

The reason for me choosing jTwig as it's what I'm used to using in frameworks/languages such as Django (python), Jekyll (ruby) and Slim (php).

I highly recommend it! Being able to extend templates in a parent/child type relationship is very flexible. I'm surprised it is not more popular with Spark users.

tipsy commented 5 years ago

I'm surprised it is not more popular with Spark users.

I think people are more likely to use Velocity and Freemarker, as they have been around forever. Most people don't use Spark for server side rendering anymore. I used Velocity before, but once I switched jobs I started using Vuejs instead.

mhulse commented 5 years ago

I think people are more likely to use Velocity and Freemarker, as they have been around forever.

Ahh, that makes sense!

Most people don't use Spark for server side rendering anymore. I used Velocity before, but once I switched jobs I started using Vuejs instead.

I see, yah, that also makes sense.

I'm kinda in a situation where we build a lot of websites vs. apps. For our day to day workflow, more traditional website creation fits the bill.

My eventual goal (for projects that are not specifically needing SPA treatment) is to combine both server-side spark/java routed templates and vue.js (probably) pages ... So, like a hybrid approach.

With all that said, yesterday, I came really close to getting hot-reload extension working! It was hooked into every page, yet it was not reloading when I refreshed. I think it's because the jetty server is running from a jar?

The docs say:

Integration with Spring Boot

Note that, jtwig-spring-boot-started module, by default, sets the template source as the classpath, bundling such application inside a standalone jar makes it impossible to edit such template files. For this to work, an easy solution can be to use templates stored in a specific directory, as shown in the example below:

@Configuration
public class JtwigConfig implements JtwigViewResolverConfigurer {
    @Override
    public void configure(JtwigViewResolver viewResolver) {
        viewResolver.setPrefix("file:/path/to/base/directory");
        viewResolver.setRenderer(new JtwigRenderer(EnvironmentConfigurationBuilder
                .configuration()
                .extensions().add(new HotReloadingExtension(TimeUnit.SECONDS, 1)).and()
                .build()));
    }
}

But that's for Spring... I'm guessing though, Spark handles things similarly?

Here's my basic attempt at getting it working:

build.gradle

dependencies {
  compile 'org.jtwig:jtwig-hot-reloading-extension:5.86.1.RELEASE'
  ...

JtwigTemplateEngine.java

import org.jtwig.environment.EnvironmentConfiguration;
import org.jtwig.environment.EnvironmentConfigurationBuilder;
import org.jtwig.hot.reloading.HotReloadingExtension;
import java.util.concurrent.TimeUnit;

...

@Override
public String render(ModelAndView modelAndView) {
  String viewName = templatesDirectory + "/" + modelAndView.getViewName();
  JtwigTemplate template = JtwigTemplate.classpathTemplate(viewName, JtwigTemplateEngineConfig.get());
  JtwigModel model = JtwigModel.newModel((Map) modelAndView.getModel());
  return template.render(model);
}

JtwigTemplateEngineConfig.java

package com.ieqtech.ap.plugins;

import org.jtwig.environment.EnvironmentConfiguration;
import org.jtwig.environment.EnvironmentConfigurationBuilder;
import org.jtwig.hot.reloading.HotReloadingExtension;

import java.util.concurrent.TimeUnit;

public class JtwigTemplateEngineConfig {

    private static EnvironmentConfiguration configuration;

    public static EnvironmentConfiguration get() {
        if (configuration == null) {
            configuration = EnvironmentConfigurationBuilder
                .configuration()
                .extensions()
                .add(new HotReloadingExtension(TimeUnit.SECONDS, 1))
                .and()
                .build();
        }
        return configuration;

    }

}

The above was calling the hot reload module on each request, but nothing was updating (again, I'm assuming it's due to running in a standalone jar?)

I played around with passing a file:/// path to Jtwig's constructor, and with (from Main):

System.setProperty("file.resource.loader.path", "/src/path/to/my/templates_dir/");
System.setProperty("file.resource.loader.cache", "false");

… but that did not seem to help.

Anyway, I have yet to revisit ... I'm not a java guru, but I feel like I'm close!

Thanks again @tipsy, I really appreciate you taking the time to help me out. 👍