Понимание общих шагов в настройке проекта имеет решающее значение перед погружением в специфику каждой технологии клиентского дополнения. Мои требования из последнего поста были довольно простыми:
- Я приму точку зрения бэкэнд-разработчика.
- Нет этапа сборки front-end: нет TypeScript, нет минификации и т. д.
- Все зависимости управляются из внутреннего приложения, т.е. Maven.
Важно отметить, что технология, которую я буду подробно описывать, за исключением Vaadin, следует схожему подходу. Vaadin, с его уникальной парадигмой, действительно выделяется среди подходов.
WebJars — это технология , разработанная в 2012 году Джеймсом Уордом именно для удовлетворения этих требований.
WebJars — это клиентские веб-библиотеки (например, jQuery и Bootstrap), упакованные в файлы JAR (Java Archive).
- Явное и простое управление зависимостями на стороне клиента в веб-приложениях на базе JVM
- Используйте инструменты сборки на основе JVM (например, Maven, Gradle, sbt, ...) для загрузки зависимостей на стороне клиента.
- Знайте, какие клиентские зависимости вы используете
- Транзитивные зависимости автоматически разрешаются и опционально загружаются через RequireJS
- Развернуто на Maven Central
- Публичный CDN, любезно предоставленный JSDelivr
-- Сайт Webjars
WebJar — это обычный JAR, содержащий веб-активы. Добавление WebJar к зависимостям проекта не представляет собой ничего особенного:
<dependencies> <dependency> <groupId>org.webjars.npm</groupId> <artifactId>alpinejs</artifactId> <version>3.14.1</version> </dependency> </dependencies>
Ответственность фреймворка заключается в том, чтобы выставлять активы по URL. Например, Spring Boot делает это в классе 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/**"
Внутри JAR вы можете получить доступ к ресурсам по их соответствующему пути и имени. Согласованная структура заключается в том, чтобы хранить ресурсы внутри resources/webjars/<library>/<version>
. Вот структура 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
В Spring Boot вы можете получить доступ к неминифицированной версии с помощью /webjars/alpinejs/3.14.1/dist/cdn.js
.
Разработчики выпускают клиентские библиотеки довольно часто. Когда вы меняете версию зависимости в POM, вы должны изменить путь к интерфейсу, возможно, в нескольких местах. Это скучно, не имеет никакой дополнительной ценности, и вы рискуете пропустить изменение.
Проект WebJars Locator направлен на то, чтобы избежать всех этих проблем, предоставляя путь без версии, т. е . /webjars/alpinejs/dist/cdn.js
. Вы можете добиться этого, добавив JAR-файл webjars-locator
в свои зависимости:
<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>
Я буду использовать этот подход для каждой фронтенд-технологии. Я также добавлю библиотеку Bootstrap CSS, чтобы обеспечить более привлекательный пользовательский интерфейс.
Thymeleaf — это технология рендеринга на стороне сервера.
Thymeleaf — это современный серверный шаблонизатор Java для веб- и автономных сред.
Основная цель Thymeleaf — привнести в ваш рабочий процесс разработки элегантные естественные шаблоны — HTML, которые могут корректно отображаться в браузерах, а также работать в качестве статических прототипов, обеспечивая более тесное сотрудничество в группах разработчиков.
Благодаря модулям для Spring Framework, множеству интеграций с вашими любимыми инструментами и возможности подключать собственные функции Thymeleaf идеально подходит для современной веб-разработки на базе HTML5 JVM, хотя он может делать гораздо больше.
-- Тимелист
Я был еще консультантом, когда впервые узнал о Thymeleaf. В то время Java Server Pages были на закате своей жизни. Java Server Faces пытались заменить их; IMHO, они потерпели неудачу.
Я думал, что Thymeleaf — это фантастический подход: он позволяет вам видеть результаты в статической среде во время проектирования и в серверной среде во время разработки. Еще лучше, вы можете легко перемещаться между ними, используя один и тот же файл. Я никогда не видел, чтобы эта возможность использовалась.
Однако Spring Boot полностью поддерживает Thymeleaf. Вишенка на торте: последний доступен через пространство имен HTML на странице. Если вы не купили JSF (спойлер: я не купил), Thymeleaf — это сегодняшний язык шаблонизации SSR.
Вот демонстрационный пример с сайта:
<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>
Вот Thymeleaf 101, на случай, если вам нужно ознакомиться с технологией.
Name
и Price
. Когда вы используете его на сервере, Thymeleaf включается и отображает значение, вычисленное из th:text
, #{msgs.headers.name}
и #{msgs.headers.price}
.$
запрашивает компонент Spring с тем же именем, которое было передано в модель. ${prod.name}
эквивалентно model.getBean("prod").getName()"
.#
вызывает функцию.th:each
допускает циклы.Большинство, если не все, фронтенд-фреймворки работают с клиентской моделью. Нам нужно навести мост между серверной и клиентской моделями.
Я использую следующий код на стороне сервера:
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 ) } } } }
Определите класс Todo
.
Добавьте список в памяти к фабрике компонентов. В обычном приложении вы бы использовали Repository
для чтения из базы данных.
Создайте HTML-шаблон.
Шаблон — src/main/resources/templates/index.html
с атрибутами Thymeleaf.
Поместите модель в контекст страницы.
Thymeleaf предлагает атрибут th:inline="javascript"
в теге <script>
. Он отображает данные на стороне сервера как переменные JavaScript. Документация объясняет это гораздо лучше, чем я когда-либо мог:
Первое, что мы можем сделать с помощью встраивания скриптов, — это записать значения выражений в наши скрипты, например:
<script th:inline="javascript"> /*<![CDATA[*/ ... var username = /*[[${session.user.name}]]*/ 'Sebastian'; ... /*]]>*/ </script>
Синтаксис
/*[[...]]*/
предписывает Thymeleaf оценить содержащееся выражение. Но здесь есть и другие последствия:
Поскольку наше выражение является комментарием JavaScript
(/*...*/)
, оно будет проигнорировано при статическом отображении страницы в браузере.Код после встроенного выражения (
'Sebastian'
) будет выполнен при статическом отображении страницы.Thymeleaf выполнит выражение и вставит результат, но также удалит весь код в строке после самого встроенного выражения (часть, которая выполняется при статическом отображении).
Если мы применим вышеизложенное к нашему коду, мы можем получить атрибуты модели, переданные Spring, как:
<script th:inline="javascript"> /*<![CDATA[*/ window.title = /*[[${title}]]*/ 'A Title' window.todos = /*[[${todos}]]*/ [{ 'id': 1, 'label': 'Take out the trash', 'completed': false }] /*]]>*/ </script>
При рендеринге на стороне сервера результат будет следующим:
<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>
В этом посте я описал два компонента, которые буду использовать на протяжении всей оставшейся части этой серии:
Полный исходный код этой статьи можно найти на GitHub.
Идем дальше:
Первоначально опубликовано в A Java Geek 15 сентября 2024 г.