Qute Templating Engine

Qute es un motor de plantillas desarrollado específicamente para Quarkus. Se minimiza el uso de la reflexión para reducir el tamaño de las imágenes nativas. La API combina tanto el estilo de codificación imperativo como el reactivo no bloqueante. En el modo de desarrollo, todos los archivos ubicados en src/main/resources/templates se supervisan en busca de cambios, y las modificaciones se hacen visibles inmediatamente. Además, nuestro objetivo es detectar la mayoría de los problemas de plantilla en el momento de la compilación. En esta guía, aprenderá a renderizar fácilmente plantillas en su aplicación.

Solución

Recomendamos que siga las instrucciones de las siguientes secciones y cree la aplicación paso a paso. Sin embargo, también puede ir directamente al ejemplo completo.

Clone el repositorio Git: git clone -b 3.20 https://github.com/quarkusio/quarkus-quickstarts.git o descargue un archivo.

La solución se encuentra en el qute-quickstart directorio.

Servir plantillas Qute a través de HTTP

Si desea servir sus plantillas a través de HTTP:

  1. La extensión Web de Qute le permite servir directamente a través de HTTP plantillas ubicadas en src/main/resources/templates/pub/. En ese caso no necesita ningún código Java para "conectar" la plantilla, por ejemplo, la plantilla src/main/resources/templates/pub/foo.html se servirá desde las rutas /foo y /foo.html por defecto.

  2. Para un control más fino, puede combinarlo con Quarkus REST para controlar cómo se servirá su plantilla. Todos los archivos ubicados en el directorio src/main/resources/templates y sus subdirectorios se registran como plantillas y pueden inyectarse en un recurso REST.

pom.xml
<dependency>
    <groupId>io.quarkiverse.qute.web</groupId>
    <artifactId>quarkus-qute-web</artifactId>
</dependency>
build.gradle
implementation("io.quarkiverse.qute.web:quarkus-qute-web")
The Qute Web extension, while hosted in the Quarkiverse, is part of the Quarkus Platform and its version is defined in the Quarkus Platform BOM.

Sirviendo Hola Mundo con Qute

Empecemos con una plantilla Hola Mundo:

src/main/resources/templates/pub/hello.html
<h1>Hello {http:param('name', 'Quarkus')}!</h1> (1)
1 {http:param('name', 'Quarkus')} es una expresión que se evalúa cuando se renderiza la plantilla (Quarkus es el valor por defecto).
Templates located in the pub directory are served via HTTP. This behavior is built-in, no controllers are needed. For example, the template src/main/resources/templates/pub/foo.html will be served from the paths /foo and /foo.html by default.

Una vez que su aplicación se esté ejecutando, puede abrir su navegador y navegar a: http://localhost:8080/hello?name=Martin

Para más información sobre las opciones de Qute Web, consulte la guía de Qute Web.

Hola Qute y REST

For finer control, you can combine Qute Web with Quarkus REST or Quarkus RESTEasy to control how your template will be served

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-rest")

Una plantilla de texto muy sencilla:

hello.txt
Hello {name}! (1)
1 {name} es una expresión de valor que se evalúa cuando se renderiza la plantilla.

Ahora vamos a inyectar la plantilla "compilada" en la clase de recursos.

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("hello")
public class HelloResource {

    @Inject
    Template hello; (1)

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return hello.data("name", name); (2) (3)
    }
}
1 Si no se proporciona el calificador @Location, se utiliza el nombre del campo para localizar la plantilla. En este caso particular, estamos inyectando una plantilla con la ruta templates/hello.txt.
2 Template.data() devuelve una nueva instancia de plantilla que se puede personalizar antes de que se active el renderizado real. En este caso, ponemos el valor del nombre bajo la clave name. El mapa de datos es accesible durante el renderizado.
3 Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.

Si su aplicación se está ejecutando, puede solicitar el punto final:

$ curl -w "\n" http://localhost:8080/hello?name=Martin
Hello Martin!

Plantillas con seguridad de tipos

Hay una forma alternativa de declarar sus plantillas en su código Java, que se basa en la siguiente convención:

  • Organice sus archivos de plantillas en el directorio /src/main/resources/templates, agrupándolos en un directorio por cada clase de recurso. Así, si su clase FruitResource hace referencia a dos plantillas apples y oranges, colóquelas en /src/main/resources/templates/FruitResource/apples.txt y /src/main/resources/templates/FruitResource/oranges.txt. Agrupar las plantillas por clase de recurso facilita la navegación hasta ellas.

  • En cada una de sus clases de recursos, declare una clase @CheckedTemplate static class Template {} dentro de su clase de recursos.

  • Declare un public static native TemplateInstance method(); por archivo de plantilla para su recurso.

  • Utilice esos métodos estáticos para construir sus instancias de plantilla.

Este es el ejemplo anterior, reescrito con este estilo:

Empezaremos con una plantilla muy sencilla:

HelloResource/hello.txt
Hello {name}! (1)
1 {name} es una expresión de valor que se evalúa cuando se renderiza la plantilla.

Ahora vamos a declarar y utilizar esta plantilla en la clase de recursos.

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.CheckedTemplate;

@Path("hello")
public class HelloResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance hello(String name); (1)
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return Templates.hello(name); (2)
    }
}
1 Esto declara una plantilla con la ruta templates/HelloResource/hello.
2 Templates.hello() returns a new template instance that is returned from the resource method. Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.
Once you have declared a @CheckedTemplate class, we will check that all its methods point to existing templates, so if you try to use a template from your Java code and you forgot to add it, we will let you know at build time :)

Tenga en cuenta que este estilo de declaración le permite referenciar plantillas declaradas en otros recursos también:

GreetingResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;

@Path("greeting")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return HelloResource.Templates.hello(name);
    }
}

Plantillas de nivel superior con seguridad de tipos

Naturalmente, si desea declarar plantillas en el nivel superior, directamente en /src/main/resources/templates/hello.txt, por ejemplo, puede declararlas en una clase de nivel superior (no anidada) Templates:

HelloResource.java
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@CheckedTemplate
public class Templates {
    public static native TemplateInstance hello(String name); (1)
}
1 Esto declara una plantilla con la ruta templates/hello.

Declaraciones de parámetros de plantillas

Si declara una declaración de parámetro en una plantilla, Qute intenta validar todas las expresiones que hacen referencia a este parámetro y si se encuentra una expresión incorrecta la compilación falla.

Supongamos que tenemos una clase simple como esta:

Item.java
public class Item {
    public String name;
    public BigDecimal price;
}

Y nos gustaría renderizar una página HTML simple que contenga el nombre del artículo y el precio.

Empecemos de nuevo con la plantilla:

ItemResource/item.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title> (1)
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div> (2)
</body>
</html>
1 Esta expresión está validada. Intente cambiar la expresión a {item.nonSense} y la compilación debería fallar.
2 Esto también se valida.

Por último, vamos a crear una clase de recursos con plantillas con seguridad de tipos:

ItemResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@Path("item")
public class ItemResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance item(Item item); (1)
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(@PathParam("id") Integer id) {
        return Templates.item(service.findItem(id)); (2)
    }
}
1 Declare un método que nos da un TemplateInstance para templates/ItemResource/item.html y declare su parámetro Item item para que podamos validar la plantilla.
2 Haga que el objeto Item sea accesible en la plantilla.
When the --parameters compiler argument is enabled, Quarkus REST may infer the parameter names from the method argument names, making the @PathParam("id") annotation optional in this case.

Declaración de parámetros de la plantilla dentro de la propia plantilla

Alternativamente, puede declarar los parámetros de la plantilla en el propio archivo de la plantilla.

Empecemos de nuevo con la plantilla:

item.html
{@org.acme.Item item} (1)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title> (2)
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div>
</body>
</html>
1 Declaración opcional del parámetro. Qute intenta validar todas las expresiones que hacen referencia al parámetro item.
2 Esta expresión está validada. Intente cambiar la expresión a {item.nonSense} y la compilación debería fallar.

Por último, vamos a crear una clase de recursos.

ItemResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("item")
public class ItemResource {

    @Inject
    ItemService service;

    @Inject
    Template item; (1)

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(Integer id) {
        return item.data("item", service.findItem(id)); (2)
    }
}
1 Inyecte la plantilla con la ruta templates/item.html.
2 Haga que el objeto Item sea accesible en la plantilla.

Métodos de extensión de plantillas

Los métodos de extensión de plantillas se utilizan para ampliar el conjunto de propiedades accesibles de los objetos de datos.

A veces, no tiene el control de las clases que desea utilizar en su plantilla y no puede añadirles métodos. Los métodos de extensión de plantillas le permiten declarar nuevos métodos para esas clases que estarán disponibles desde sus plantillas como si pertenecieran a la clase de destino.

Sigamos ampliando nuestra página HTML simple que contiene el nombre del artículo, el precio y añadamos un precio con descuento. El precio con descuento a veces se denomina "propiedad computada". Implementaremos un método de extensión de plantilla para renderizar esta propiedad fácilmente. Actualicemos nuestra plantilla:

HelloResource/item.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title>
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div>
    {#if item.price > 100} (1)
    <div>Discounted Price: {item.discountedPrice}</div> (2)
    {/if}
</body>
</html>
1 if es una sección de flujo de control básico.
2 Esta expresión también se valida contra la clase Item y, obviamente, no hay tal propiedad declarada. Sin embargo, hay un método de extensión de plantilla declarado en la clase TemplateExtensions - véase más abajo.

Por último, vamos a crear una clase en la que pondremos todos nuestros métodos de extensión:

TemplateExtensions.java
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateExtension;

@TemplateExtension
public class TemplateExtensions {

    public static BigDecimal discountedPrice(Item item) { (1)
        return item.price.multiply(new BigDecimal("0.9"));
    }
}
1 Se puede utilizar un método de extensión de plantilla estática para añadir "propiedades computadas" a una clase de datos. La clase del primer parámetro se utiliza para hacer coincidir el objeto base y el nombre del método se utiliza para hacer coincidir el nombre de la propiedad.
you can place template extension methods in every class if you annotate them with @TemplateExtension but we advise to keep them either grouped by target type, or in a single TemplateExtensions class by convention.

Renderizado de informes periódicos

El motor de plantillas también puede ser muy útil para renderizar informes periódicos. Primero tendrá que añadir las extensiones quarkus-scheduler y quarkus-qute. En su archivo pom.xml, añada:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-qute</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-scheduler</artifactId>
</dependency>

Supongamos que tenemos un bean SampleService cuyo método get() devuelve una lista de muestras.

Sample.java
public class Sample {
    public boolean valid;
    public String name;
    public String data;
}

La plantilla es simple:

report.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Report {now}</title>
</head>
<body>
    <h1>Report {now}</h1>
    {#for sample in samples} (1)
      <h2>{sample.name ?: 'Unknown'}</h2> (2)
      <p>
      {#if sample.valid}
        {sample.data}
      {#else}
        <strong>Invalid sample found</strong>.
      {/if}
      </p>
    {/for}
</body>
</html>
1 La sección de bucle permite iterar sobre iterables, mapas y flujos.
2 Esta expresión de valor está utilizando el operador elvis - si el nombre es nulo se utiliza el valor por defecto.
ReportGenerator.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;

import io.quarkus.qute.Template;
import io.quarkus.qute.Location;
import io.quarkus.scheduler.Scheduled;

public class ReportGenerator {

    @Inject
    SampleService service;

    @Location("reports/v1/report_01") (1)
    Template report;

    @Scheduled(cron="0 30 * * * ?") (2)
    void generate() {
        String result = report
            .data("samples", service.get())
            .data("now", java.time.LocalDateTime.now())
            .render(); (3)
        // Write the result somewhere...
    }
}
1 En este caso, utilizamos el calificador @Location para especificar la ruta de la plantilla: templates/reports/v1/report_01.html.
2 Utilice la anotación @Scheduled para indicar a Quarkus que ejecute este método cada media hora. Para más información, consulte la guía del Programador.
3 El método TemplateInstance.render() desencadena la renderización. Tenga en cuenta que este método bloquea el hilo actual.

Guía de referencia Qute

Para saber más sobre Qute, consulte la guía de referencia de Qute.

Referencia de configuración de Qute

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Tipo

Por defecto

The list of suffixes used when attempting to locate a template file.

By default, engine.getTemplate("foo") would result in several lookups: foo, foo.html, foo.txt, etc.

Environment variable: QUARKUS_QUTE_SUFFIXES

Show more

list of string

qute.html,qute.txt,html,txt

The additional map of suffixes to content types. This map is used when working with template variants. By default, the java.net.URLConnection#getFileNameMap() is used to determine the content type of a template file.

Environment variable: QUARKUS_QUTE_CONTENT_TYPES__FILE_SUFFIX_

Show more

Map<String,String>

The list of exclude rules used to intentionally ignore some parts of an expression when performing type-safe validation.

An element value must have at least two parts separated by dot. The last part is used to match the property/method name. The prepended parts are used to match the class name. The value * can be used to match any name.

Examples:

  • org.acme.Foo.name - exclude the property/method name on the org.acme.Foo class

  • org.acme.Foo.* - exclude any property/method on the org.acme.Foo class

  • *.age - exclude the property/method age on any class

Environment variable: QUARKUS_QUTE_TYPE_CHECK_EXCLUDES

Show more

list of string

This regular expression is used to exclude template files found in template roots. Excluded templates are neither parsed nor validated during build and are not available at runtime.

The matched input is the file path relative from the root directory and the / is used as a path separator.

By default, the hidden files are excluded. The name of a hidden file starts with a dot.

Environment variable: QUARKUS_QUTE_TEMPLATE_PATH_EXCLUDE

Show more

Pattern

^\..|.\/\..*$

The prefix is used to access the iteration metadata inside a loop section.

A valid prefix consists of alphanumeric characters and underscores. Three special constants can be used:

  • <alias_> - the alias of an iterated element suffixed with an underscore is used, e.g. item_hasNext and it_count

  • <alias?> - the alias of an iterated element suffixed with a question mark is used, e.g. item?hasNext and it?count

  • <none> - no prefix is used, e.g. hasNext and count

By default, the <alias_> constant is set.

Environment variable: QUARKUS_QUTE_ITERATION_METADATA_PREFIX

Show more

string

<alias_>

The list of content types for which the ', ", <, > and & characters are escaped if a template variant is set.

Environment variable: QUARKUS_QUTE_ESCAPE_CONTENT_TYPES

Show more

list of string

text/html,text/xml,application/xml,application/xhtml+xml

The default charset of the templates files.

Environment variable: QUARKUS_QUTE_DEFAULT_CHARSET

Show more

Charset

UTF-8

By default, a template modification results in an application restart that triggers build-time validations.

This regular expression can be used to specify the templates for which the application is not restarted. I.e. the templates are reloaded and only runtime validations are performed.

The matched input is the template path that starts with a template root, and the / is used as a path separator. For example, templates/foo.html.

Environment variable: QUARKUS_QUTE_DEV_MODE_NO_RESTART_TEMPLATES

Show more

Pattern

By default, the rendering results of injected and type-safe templates are recorded in the managed RenderedResults which is registered as a CDI bean.

Environment variable: QUARKUS_QUTE_TEST_MODE_RECORD_RENDERED_RESULTS

Show more

boolean

true

The strategy used when a standalone expression evaluates to a "not found" value at runtime and the quarkus.qute.strict-rendering config property is set to false

This strategy is never used when evaluating section parameters, e.g. {#if foo.name}. In such case, it’s the responsibility of the section to handle this situation appropriately.

By default, the NOT_FOUND constant is written to the output. However, in the development mode the PropertyNotFoundStrategy#THROW_EXCEPTION is used by default, i.e. when the strategy is not specified.

Environment variable: QUARKUS_QUTE_PROPERTY_NOT_FOUND_STRATEGY

Show more

defaultOutput the NOT_FOUND constant., noopNo operation - no output., throw-exceptionThrow a TemplateException., output-originalOutput the original expression string, e.g. {foo.name}.

Specify whether the parser should remove standalone lines from the output. A standalone line is a line that contains at least one section tag, parameter declaration, or comment but no expression and no non-whitespace character.

Environment variable: QUARKUS_QUTE_REMOVE_STANDALONE_LINES

Show more

boolean

true

If set to true then any expression that is evaluated to a Results.NotFound value will always result in a TemplateException and the rendering is aborted.

Note that the quarkus.qute.property-not-found-strategy config property is completely ignored if strict rendering is enabled.

Environment variable: QUARKUS_QUTE_STRICT_RENDERING

Show more

boolean

true

The global rendering timeout in milliseconds. It is used if no timeout template instance attribute is set.

Environment variable: QUARKUS_QUTE_TIMEOUT

Show more

long

10000

If set to true then the timeout should also be used for asynchronous rendering methods, such as TemplateInstance#createUni() and TemplateInstance#renderAsync().

Environment variable: QUARKUS_QUTE_USE_ASYNC_TIMEOUT

Show more

boolean

true

Related content

On the same extensions