Qute is primarily designed as a Quarkus extension. It is possible to use it as a "standalone" library too. However, in such case some of the features are not available. In general, any feature mentioned under the Quarkus Integration section is missing. You can find more information about the limitations and possibilities in the Qute Used as a Standalone Library section.
Sure thing, here we go!
This project is in its core a port of the Quarkus Qute configuration to Spring Boot enabling various Spring specific features to be used instead of Quarkus's. It demonstrates how to set up and use Qute in a Spring Boot application, offering a better alternative to other templating engines like for example Thymeleaf.
{@String name = 'World'}
{#include layout}
{#title}A Qute Readme{/title}
{#content}
<h1>
Hello {name}!
</h1>
<p>{msg:t('readme.welcome-message')}</p>
{/content}
{/include}
- Qute supports implementations of component libraries (see example-component-lib and example-backend as reference implementation)
- Qute natively supports slotting (unlike thymeleaf)
- Qute has a pleasant syntax (unlike thymeleaf)
- Qute supports improved developer-experience (ex. hot-reloading capabilities)
- Qute is backed by major corporations (whoever works on Quarkus)
- Qute is actively maintained (unlike thymeleaf)
For a rough overview of Qute's features read the Qute Reference Guide
Add required dependency
<dependency>
<groupId>net.snemeis</groupId>
<artifactId>qute-spring-boot-starter</artifactId>
<version>${version.qute-starter}</version>
</dependency>
The starter then autoconfigures a Qute engine instance and a template ViewResolver.
{!/templates/index.qute.html!}
{@java.lang.String name}
Hello {name}!
// MainController.java
@GetMapping("/")
public String index(Model model) {
model.addAttribute("name", "World");
return "index";
}
Alternatively to resolving templates using the ViewResolver the Engine instance
can also be accessed directly by calling Qute.engine()
.
One notable difference is that the template cache is not being cleared
when the cache is turned off via spring.qute.cache-enabled=false
.
@GetMapping("/")
@ResponseBody
public String index() {
return Qute.engine()
.getTemplate("index")
.data(Map.of("name", "World"))
.render();
}
// or
@GetMapping("/")
@ResponseBody
public String index() {
return Qute.fmt('Hello {name}!')
.data(Map.of("name", "World"))
.render();
}
For all configuration options of this starter take a look at the qute properties file.
For the quarkus configuration and their explanations: Qute Reference Guide 4.15. Configuration Reference. (The ones for this starter are leaned on the original implementation).
Example configuration for local development:
# to load templates from filepath instead of classpath (direct editing feedback)
# good if coupled with something like:
# spring.resources.static-locations=file:///${user.dir}/src/main/resources/static/
spring.qute.dev-mode = true
# set path to filepath
spring.qute.dev-prefix = ${user.dir}/src/main/resources/templates/
# for hot-reloading in dev-mode
# (tip: good when coupled with custom vite config to force page reload on file save
# https://github.com/ElMassimo/vite-plugin-full-reload, see laravel using this for example)
spring.qute.caching-enabled = false
# to be able to resolve filenames like 'index', 'index.html' or 'index.qute.html'
spring.qute.suffixes = ,.html,.qute.html
# to get parsing mistakes in the templates
logging.level.io.quarkus=DEBUG
Beans of type ValueResolver
and NamespaceResolver
are collected
and registered as resolvers on initialization.
(As well as ParserHooks
because the original implementation had this as well)
Example:
@Configuration
class ValueResolverConfig {
@Bean
ValueResolver valueResolverA() {
return new ValueResolverA();
}
}
For a general reference see the Qute Reference Guide.
The spring related changes are the following.
Since Spring Boot also has beans which can be referenced at runtime, this feature is also supported
via the cdi
and inject
namespaces. So the following snippet is 100% valid:
{cdi:someService.findSomeValue(10).name}
{inject:otherService.fetchSomethingElse()}
This starter does not support type-safe message bundles, however some part of the syntax to retrieve messages from a message bundle is still valid:
{msg:t('some-key', 'with', 'params', 'for the message')}
{msg:t('some-other-key', oneVariable, 42)}