Es fundamental comprender los pasos compartidos en la configuración del proyecto antes de profundizar en los detalles de cada tecnología que amplía el cliente. Mis requisitos de la última publicación eran bastante claros:
- Asumiré el punto de vista de un desarrollador backend.
- No hay ningún paso de construcción del frontend: no hay TypeScript, no hay minimización, etc.
- Todas las dependencias se administran desde la aplicación backend, es decir , Maven
Es importante señalar que la tecnología que voy a describir en detalle, a excepción de Vaadin, sigue un enfoque similar. Vaadin, con su paradigma único, realmente se destaca entre los enfoques.
WebJars es una tecnología diseñada en 2012 por James Ward para gestionar estos requisitos exactos.
Los WebJars son bibliotecas web del lado del cliente (por ejemplo, jQuery y Bootstrap) empaquetadas en archivos JAR (Java Archive).
- Administre de forma explícita y sencilla las dependencias del lado del cliente en aplicaciones web basadas en JVM
- Utilice herramientas de compilación basadas en JVM (por ejemplo, Maven, Gradle, sbt, ...) para descargar sus dependencias del lado del cliente
- Sepa qué dependencias del lado del cliente está utilizando
- Las dependencias transitivas se resuelven automáticamente y se cargan opcionalmente a través de RequireJS
- Implementado en Maven Central
- CDN público, proporcionado generosamente por JSDelivr
Un WebJar es un JAR normal que contiene recursos web. Agregar un WebJar a las dependencias de un proyecto no es nada específico:
<dependencies> <dependency> <groupId>org.webjars.npm</groupId> <artifactId>alpinejs</artifactId> <version>3.14.1</version> </dependency> </dependencies>
La responsabilidad del framework es exponer los recursos bajo una URL. Por ejemplo, Spring Boot lo hace en la clase WebMvcAutoConfiguration
:
public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), //1 "classpath:/META-INF/resources/webjars/"); addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); if (this.servletContext != null) { ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); registration.addResourceLocations(resource); } }); }
"/webjars/**"
Dentro del JAR, puedes acceder a los recursos por su respectiva ruta y nombre. La estructura acordada es almacenar los recursos dentro resources/webjars/<library>/<version>
. Esta es la estructura de alpinejs-3.14.1.jar
:
META-INF |_ MANIFEST.MF |_ maven.org.webjars.npm.alpinejs |_ resources.webjars.alpinejs.3.14.1 |_ builds |_ dist |_ cdn.js |_ cdn.min.js |_ src |_ package.json
Dentro de Spring Boot, puedes acceder a la versión no minimizada con /webjars/alpinejs/3.14.1/dist/cdn.js
.
Los desarrolladores lanzan bibliotecas del lado del cliente con bastante frecuencia. Cuando se cambia una versión de dependencia en el POM, se debe cambiar la ruta del frontend, posiblemente en varias ubicaciones. Es aburrido, no tiene valor agregado y se corre el riesgo de perderse un cambio.
El proyecto WebJars Locator tiene como objetivo evitar todos estos problemas al proporcionar una ruta sin versión, es decir , /webjars/alpinejs/dist/cdn.js
. Puede lograrlo agregando el JAR webjars-locator
a sus dependencias:
<dependencies> <dependency> <groupId>org.webjars.npm</groupId> <artifactId>alpinejs</artifactId> <version>3.14.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> <version>0.52</version> </dependency> </dependencies>
Utilizaré este enfoque para cada tecnología de interfaz de usuario. También agregaré la biblioteca CSS de Bootstrap para brindar una interfaz de usuario más atractiva.
Thymeleaf es una tecnología de renderizado del lado del servidor.
Thymeleaf es un moderno motor de plantillas Java del lado del servidor para entornos web e independientes.
El objetivo principal de Thymeleaf es incorporar plantillas naturales y elegantes a su flujo de trabajo de desarrollo: HTML que pueda mostrarse correctamente en los navegadores y también funcionar como prototipos estáticos, lo que permite una colaboración más sólida en los equipos de desarrollo.
Con módulos para Spring Framework, una gran cantidad de integraciones con sus herramientas favoritas y la capacidad de conectar su propia funcionalidad, Thymeleaf es ideal para el desarrollo web HTML5 JVM moderno, aunque hay mucho más que puede hacer.
Yo todavía era consultor cuando me enteré de Thymeleaf. En ese momento, Java Server Pages estaba llegando al final de su vida útil. Java Server Faces intentaba reemplazarlas; en mi humilde opinión, fracasaron.
Pensé que Thymeleaf era un enfoque fantástico: permite ver los resultados en un entorno estático en tiempo de diseño y en un entorno de servidor en tiempo de desarrollo. Mejor aún, puedes pasar sin problemas de uno a otro utilizando el mismo archivo. Nunca he visto que se utilice esta capacidad.
Sin embargo, Spring Boot es totalmente compatible con Thymeleaf. La guinda del pastel: este último está disponible a través de un espacio de nombres HTML en la página. Si no te convenció JSF (spoiler: yo no lo hice), Thymeleaf es el lenguaje de plantillas SSR de referencia en la actualidad.
Aquí está la muestra de demostración del sitio web:
<table> <thead> <tr> <th th:text="#{msgs.headers.name}">Name</th> <th th:text="#{msgs.headers.price}">Price</th> </tr> </thead> <tbody> <tr th:each="prod: ${allProducts}"> <td th:text="${prod.name}">Oranges</td> <td th:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td> </tr> </tbody> </table>
A continuación le presentamos un tutorial sobre Thymeleaf 101, en caso de que necesite familiarizarse con la tecnología.
Name
y Price
. Cuando lo usas en el servidor, Thymeleaf se activa y muestra el valor calculado a partir de th:text
, #{msgs.headers.name}
y #{msgs.headers.price}
.$
consulta un bean Spring del mismo nombre pasado al modelo. ${prod.name}
es equivalente a model.getBean("prod").getName()"
.#
llama a una función.th:each
permite bucles.La mayoría de los frameworks front-end, si no todos, funcionan con un modelo del lado del cliente. Necesitamos crear un puente entre el modelo del lado del servidor y el del lado del cliente.
El código del lado del servidor que estoy usando es el siguiente:
data class Todo(val id: Int, var label: String, var completed: Boolean = false) //1 fun config() = beans { bean { mutableListOf( //2 Todo(1, "Go to the groceries", false), Todo(2, "Walk the dog", false), Todo(3, "Take out the trash", false) ) } bean { router { GET("/") { ok().render( //3 "index", //4 mapOf("title" to "My Title", "todos" to ref<List<Todo>>()) //5 ) } } } }
Define la clase Todo
.
Agregue una lista en memoria a la fábrica de beans. En una aplicación normal, usaría un Repository
para leer desde la base de datos.
Renderizar una plantilla HTML.
La plantilla es src/main/resources/templates/index.html
con atributos Thymeleaf.
Coloque el modelo en el contexto de la página.
Thymeleaf ofrece un atributo th:inline="javascript"
en la etiqueta <script>
. Representa los datos del lado del servidor como variables de JavaScript. La documentación lo explica mucho mejor de lo que yo podría hacerlo:
Lo primero que podemos hacer con la inserción de scripts es escribir el valor de las expresiones en nuestros scripts, como:
<script th:inline="javascript"> /*<![CDATA[*/ ... var username = /*[[${session.user.name}]]*/ 'Sebastian'; ... /*]]>*/ </script>
La sintaxis
/*[[...]]*/
indica a Thymeleaf que evalúe la expresión contenida. Pero hay más implicaciones aquí:
Al ser un comentario de javascript
(/*...*/)
, nuestra expresión será ignorada al mostrar la página estáticamente en un navegador.El código después de la expresión en línea (
'Sebastian'
) se ejecutará al mostrar la página estáticamente.Thymeleaf ejecutará la expresión e insertará el resultado, pero también eliminará todo el código en la línea después de la expresión en línea (la parte que se ejecuta cuando se muestra estáticamente).
Si aplicamos lo anterior a nuestro código, podemos obtener los atributos del modelo pasados por Spring como:
<script th:inline="javascript"> /*<![CDATA[*/ window.title = /*[[${title}]]*/ 'A Title' window.todos = /*[[${todos}]]*/ [{ 'id': 1, 'label': 'Take out the trash', 'completed': false }] /*]]>*/ </script>
Cuando se procesa en el lado del servidor, el resultado es:
<script> /*<![CDATA[*/ window.title = "My title"; window.todos: [{"id":1,"label":"Go to the groceries","completed":false},{"id":2,"label":"Walk the dog","completed":false},{"id":3,"label":"Take out the trash","completed":false}] /*]]>*/ </script>
En esta publicación, describo dos componentes que utilizaré en el resto de esta serie:
El código fuente completo de esta publicación se puede encontrar en GitHub.
Ir más allá:
Publicado originalmente en A Java Geek el 15 de septiembre de 2024