Хотите узнать, как с помощью Nim создавать клиентские веб-приложения?
В этой статье я расскажу вам о веб-фреймворках в Nim и их возможностях, а также приведу примеры в сравнении друг с другом и другими фреймворками.


Начну с того, что в экосистеме Nim есть популярный веб-фреймворк, название которому Karax. Автором является Araq, по совместительству создатель Nim.

Andreas Rumpf
Araq, создатель Nim
Обычное HelloWorld приложение выглядит следующим образом:
include karax / prelude proc createDom(): VNode = result = buildHtml(tdiv): text "Hello, world!" setRenderer createDom
Импортируем сам фреймворк, создаем функцию, возвращающую виртуальное DOM дерево и задаем эту функцию для рендера.
Как можно видеть, подход к разработке здесь декларативен, в отличие от большинства веб-фреймворков.
Возвращаясь к теме статьи - в экосистеме Nim с недавних пор появилась альтернатива Karax - HappyX. Основным преимуществом HappyX перед Karax можно назвать макро-ориентированность. это означает, что бо́льшая часть задач выполняется во время компиляции, тем самым увеличивая производительность конечного веб-приложения.
Сравним HelloWorld приложение, написанное на HappyX с тем, что выше:
import happyx appRoutes "app": "/": "Hello, world!"
Подход к разработке здесь также в основном декларативен, однако в следующей статье я расскажу об императивном подходе к разработке в HappyX.
Обработка событий
В каждом клиентском веб-приложении присутствует обработка событий, например нажатие на кнопку, заполнение формы и так далее. Как же обработка событий выглядит в Karax и HappyX?
За основу я возьму следующую HTML страничку с кнопкой, при нажатии на которую счетчик просто увеличивается на 1.
<!DOCTYPE html> <html> <head> </head> <body> <div> <p id="counter"> 0 </p> <button onclick="increase()"> Нажми на ме��я! </button> </div> <script> let count = 0; const counter = document.getElementById("counter"); function increase() { count++; counter.innerHTML = `${count}`; } </script> </body> </html>
Пример выше, написанный на Karax выглядит следующим образом:
include karax / prelude var count = 0 proc createDom(): VNode = result = buildHtml(tdiv): p: text $count button: text "Нажми на меня!" proc onclick(ev: Event; n: VNode) = count += 1 setRenderer createDom
Здесь мы импортируем фреймворк, создаем переменную
count, далее создаем функцию, возвращающую VDOM и повторяем структуру HTML странички выше. Также на 11-й строчке видно объявление обработчика нажатия, в котором происходит увеличение переменнойcount.
И наконец, пример, написанный на HappyX:
import happyx var count = remember 0 appRoutes "app": "/": tDiv: tP: {count} tButton: "Нажми на меня!" @click: count += 1
Здесь вновь идет импорт, далее создание реактивной переменной с помощью функции
remember, после чего объявляется само приложение со структурой, описанной в HTML файле. Здесь также присутствует объявление обработчика нажатия на 11-й строчке, в котором происходит увеличениеcount.
Динамичный контент
В одностраничных приложениях контент зачастую подгружается динамически. Например во Vue.js присутствуют директивы v-if, v-else-if, v-else, а также директива v-for для отрисовки списков, как написано в документации.
И так, рассмотрим следующий пример, написанный на Vue.js:
<template> <div> <!-- Conditional Rendering: отображение заголовка в зависимости от значения showTitle --> <h1 v-if="showTitle">Добро пожаловать!</h1> <h1 v-else>Скрытый заголовок</h1> <!-- List Rendering: отображение списка пользователей --> <ul> <!-- Используем директиву v-for для перебора элементов в списке --> <li v-for="(user, index) in userList" :key="index"> <!-- Отображаем информацию о каждом пользователе --> {{ user.name }} - {{ user.email }} </li> </ul> </div> </template> <script> export default { data() { return { // Значение для условного отображения заголовка showTitle: true, // Список пользователей для отображения userList: [ { name: 'Иван', email: 'ivan@example.com' }, { name: 'Мария', email: 'maria@example.com' }, { name: 'Александр', email: 'alex@example.com' } ] }; } }; </script>
В примере выше описан небольшой пример использования условного и цикличного рендеринга.
Как же это выглядит в Karax?
include karax / prelude import strformat, json var showTitle = true # Список пользователей для отображения var userList = %*[ { "name": "Иван", "email": "ivan@example.com" }, { "name": "Мария", "email": "maria@example.com" }, { "name": "Александр", "email": "alex@example.com" } ] proc createDom(): VNode = result = buildHtml(tdiv): h1: # Conditional Rendering: отображение заголовка в зависимости от значения showTitle if showTitle: text "Добро пожаловать!" else: text "Скрытый заголовок" ul: # List Rendering: отображение списка пользователей for user in userList.items: # Отображаем информацию о каждом пользователе li: text fmt"""{user["name"]} - {user["email"]}""" setRenderer createDom
А вот так это выглядит в HappyX:
import happyx, strformat, json var showTitle = remember true # Список пользователей для отображения var userList = remember %*[ { "name": "Иван", "email": "ivan@example.com" }, { "name": "Мария", "email": "maria@example.com" }, { "name": "Александр", "email": "alex@example.com" } ] appRoutes "app": "/": tH1: # Conditional Rendering: отображение заголовка в зависимости от значения showTitle if showTitle: "Добро пожаловать!" else: "Скрытый заголовок" tUl: # List Rendering: отображение списка пользователей for user in userList.items: # Отображаем информацию о каждом пользователе tLi: {fmt"""{user["name"]} - {user["email"]}"""}
В обоих примерах на Nim можно изменять переменные showTitle и userList, а страничка будет автоматически обновляться, учитывая изменения переменных.
Помимо if-elif-else и for HappyX также поддерживает такие выражения, как case-of, when и while.
Отличие
whenотifв Nim заключается в том, что первый выполняется во время компиляции, играя некую роль директив ��репроцессора#ifdefили#ifизC++, а второй выполняется как во время компиляции, так и во время выполнения.
Компонентный Подход
В разработке большинства одностраничных приложений используется компонентный подход, дабы вести разработку более эффективно, имея при этом красивую архитектуру проекта.
Возвращаясь к Vue.js сразу приведу пример объявления компонента и его использование:
<template> <!-- Объявляем наш компонент Button --> <button @click="handleClick">{{ buttonText }}</button> </template> <script> export default { props: { // Пропс для передачи текста кнопки buttonText: { type: String, required: true } }, methods: { handleClick() { // Метод, который будет вызван при клике на кнопку console.log('Клик по кнопке!'); } } }; </script>
<template> <div> <!-- Используем компонент Button и передаем текст кнопки через пропс --> <Button buttonText="Нажми меня" /> </div> </template> <script> // Импортируем компонент Button import Button from './Button.vue'; export default { components: { // Регистрируем компонент Button для использования в текущем компоненте Button } }; </script>
Таким образом, использование компонентов значительно облегчает разработку одностраничных приложений. Однако как пример выше выглядит в Karax и HappyX?
Karax не предоставляет каких-то особенных инструментов для компонентного подхода, однако пример выше можно реализовать при помощи второй функции, которая возвращает VDOM кнопки:
include karax / prelude proc renderButton(buttonText: string): VNode = result = buildHtml(tdiv): button: text buttonText proc onclick(ev: Event; n: VNode) = echo "Клик по кнопке!" proc createDom(): VNode = result = buildHtml(tdiv): renderButton("Нажми на меня") setRenderer createDom
HappyX же, в свою очередь, предоставляет компонентный подход и пример выше будет выглядеть уже следующим образом:
import happyx component Button: buttonText: string `template`: tButton: {self.buttonText} @click: echo "Клик по кнопке!" appRoutes "app": "/": Button("Нажми на меня")
При этом HappyX не ограничивает разработчика таким подходом, поэтому при желании можно реализовать этот пример при помощи функции, вместо компонента.
На этом у меня все, как я писал выше - в следующей статье расскажу про императивный подход к разработке в HappyX.
