В прошлой статье я сравнивал HappyX и Karax, показывая их декларативный подход к разработке с помощью Nim.
В этой статье я подробнее расскажу о разработке одностраничных приложений с помощью HappyX с использованием императивного подхода к разработке.

Так как в прошлой статье сравнивался лишь сам код, я начну с того, как установить HappyX. Для начала необходимо поставить сам Nim, если он у вас стоит — отлично.
Следующий шаг — установка HappyX с помощью пакетного менеджера nimble. Ставим последнюю версию:
nimble install happyx@#head
Отлично, можно проверить установленную версию с помощью
hpx -v
При желании можно поставить любую другую версию, например для установки версии
3.5.2нужно ввести командуnimble install happyx@3.5.2
Теперь можно переходить к созданию проекта. Для этого достаточно перейти в нужную вам директорию и ввести следующую команду.
hpx create --kind HPX --name project --use-tailwind
--kind HPX— выбор типа проекта, в данном случае мы выбрали одностраничное приложение с поддержкой.hpxфайлов.--name project— выбор имени для проекта.--use-tailwind— для одностраничного приложения мы также можем включить поддержку Tailwind CSS 3.
В созданном проекте можно наблюдать файл src/main.hpx (в исходный код добавлены комментарии):
<template> <div> <!-- Использование компонента HelloWorld --> <!-- Передача параметров: userId как целое число, query и pathParam как строки --> <HelloWorld userId:int="10" query="meow" pathParam="Path Param Example"></HelloWorld> </div> </template>
Также в файле src/components/HelloWorld.hpx можем видеть следующее:
<template> <div> Hello, world! {self.userId} <!-- Подстановка значений из текущего компонента --> <p> Query is {self.query} </p> <p> pathParam is {self.pathParam} </p> </div> </template> <!-- Объявление скрипта, по желанию можно использовать здесь JavaScript, указав lang="javascript" --> <script> # Здесь можно писать на Nim echo "Hello, world!" # Здесь задаются пропсы компонента, которые можно передавать из других компонентов # Пропсам также можно задавать значение по умолчанию, как написано ниже props: userId: int = 0 query: string = "" pathParam: string = "" </script> <!-- Изолированная область стилей, применяемая только для данного компонента --> <style> .div { border-radius: 4px; background-color: #212121; color: #ffffff; } </style>
Помимо .hpx файлов в папке src находятся index.html и router.json. В первом нет ничего интересного кроме подключения скомпилированного js файла, а вот со вторым разберемся под��обнее:
{ "/": "main.hpx", "/user{ARG1:int}/{ARG2?:string}": { "component": "HelloWorld", "args": { "userId": "ARG1", "query": { "name": "q", "type": "query" }, "pathParam": { "name": "ARG2", "type": "path" } } } }
В файле router.json описаны маршруты вашего приложения, здесь вы можете изменять маршруты, задавать точки входа компонентов а также задавать параметры к маршрутам (query либо path).
Для того, чтобы запустить проект, достаточно находиться в папке с проектом (cd project). Теперь вы сможете наблюдать все изменения файлов в реальном времени.
hpx dev --reload
Попробуем изменить src/main.hpx следующим образом и сохранить файл:
<template> <div> <h1 h-if="self.showTitle()"> Добро пожаловать! </h1> <h1 h-else> Скрытый заголовок </h1> </div> </template> <script> <!-- Объявим метод компонента - showTitle --> proc showTitle(): bool = true </script>
В <template></template> можем наблюдать применение директив h-if и h-else, некие аналоги v-if и v-else из Vue.js. В <script></script> находится объявление метода showTitle, который возвращает true.

src/main.hpxКак можно видеть - отображается лишь "Добро пожаловать!". Это происходит потому, что метод showTitle() всегда возвращает true.
Попробуем изменить скрипт:
<script> import random randomize() proc showTitle(): bool = rand(100) > 50 </script>
Здесь мы добавили импорт стандартной библиотеки Nim — random, далее единожды вызываем функцию randomize(), теперь метод showTitle будет возвращать случайное значение. Попробуйте перезагрузить страницу несколько раз. Каждый раз надпись меняется!
Теперь попробуем реализовать собственный компонент, который будем использовать в src/main.hpx. Пусть этот компонент будет обычным счетчиком. Создадим файл src/components/Counter.hpx и запишем в него следующее:
<template> <div class="flex justify-around rounded-md border-2 border-gray-900 bg-gray-300 px-2 w-24"> <div class="flex w-full self-center">{self.count}</div> <div class="flex flex-col text-sm"> <button h-onclick="self.increase()">?</button> <button h-onclick="self.decrease()">?</button> </div> </div> </template> <script> # Объявляем пропсы props: count: int = 0 # Метод для увеличения счетчика proc increase() = self.count += 1 # Метод для уменьшения счетчика proc decrease() = self.count -= 1 </script>
В этом компоненте мы используем новую директиву — h-onclick. С помощью нее мы можем обращаться к методам либо писать какой‑то короткий Nim код.
Теперь обновим файл src/main.hpx, чтобы использовать в нем наш счетчик:
<template> <div> <h1 h-if="self.showTitle()"> Добро пожаловать! </h1> <h1 h-else> Скрытый заголовок </h1> <div class="p-2"> <Counter></Counter> </div> </div> </template>
Как вы видите - достаточно лишь использовать тег <Counter></Counter>, чтобы использовать наш компонент. Также мы можем передать в него начальное значение счетчика:
<div class="p-2"> <!-- При передаче указываем, что это целое число, так как count в компоненте - целое число --> <Counter count:int="5"></Counter> </div>
Ниже можно видеть результат, который у нас получился:

h-if и два счетчикаДобавим также верхний и нижний порог счетчика:
<script> # Объявляем пропсы props: count: int = 0 floor: int = int.low ceil: int = int.high # Метод для увеличения счетчика proc increase() = if self.ceil <= self.count: return self.count += 1 # Метод для уменьшения счетчика proc decrease() = if self.floor >= self.count: return self.count -= 1 </script>
Здесь мы добавили два пропса: ceil и floor, чтобы задать верхний и нижний порог соответственно. В начале методов мы добавили проверку, которая остановит функцию в случае достижения нижнего или верхнего порога.
Посмотрим, как это выглядит в src/main.hpx:
<div class="p-2"> <Counter count:int="5" floor:nim="0" ceil:nim="10"></Counter> </div>
Тут мы добавили передачу порогов с указанием :nim, с помощью которого тип автоматически распознается.
Маршрутизация
Теперь перейдем к маршрутизации, и, возвращаясь к src/router.json сразу добавим новый маршрут, по которому будет доступен только компонент с нашим счетчиком, при это мы сможем передавать ему значения.
{ "/": "main.hpx", "/user{ARG1:int}/{ARG2?:string}": { "component": "HelloWorld", "args": { "userId": "ARG1", "query": { "name": "q", "type": "query" }, "pathParam": { "name": "ARG2", "type": "path" } } }, "/counter/{value:int}": { "component": "Counter", "args": { "count": "value" } } }
Теперь попробуем зайти на http://localhost:5000/#/counter/10:

Попробуйте поиграться с адресной строкой — значение меняется.
Теперь добавим маршрут, в котором можно регулировать порог счетчика:
"/counter/{value:int}": { "component": "Counter", "args": { "count": "value" } }, "/counter/{value:int}/{floor:int}-{ceil:int}": { "component": "Counter", "args": { "count": "value", "floor": "floor", "ceil": "ceil" } }
Таким образом можно настраивать маршруты любой сложности и взаимодействовать с ними.
Директивы
В примерах выше уже были использованы директивы h-if и h-else, однако кроме них также есть ряд директив.
h-if, h-elif, h-else служат для условного рендеринга:
<template> <h1 h-if="5 > 10">5 больше 10</h1> <h1 h-elif="5 == 10">5 = 10</h1> <h1 h-else>5 меньше 10</h1> </template>
директива h-for служит для цикличного рендеринга:
<template> <div> <div h-for="i in [1, 2, 3, 4]"> {i} <div h-for="j in i..10"> {i + j} </div> </div> </div> </template>
Помимо h-for для цикличного рендеринга может использоваться h-while:
<template> <script lang="nim"> # Здесь мы пишем на Nim var x = 1 </script> <div class="flex"> <div h-while="x <= 3"> {x} <script lang="nim"> # Здесь мы пишем на Nim x += 1 </script> </div> </div> </template>
Также существуют директивы для обработки событий. Они начинаются с h-on, например
h-onclick- обрабатывает нажатие мыши на элемент (click event);h-onmouseover- срабатывает при наведении мыши на элемент (mouseover event);h-onwheel- срабатывает при прокрутке колесика мыши (wheel event).
В каждом обработчике событий можно отправлять Event.
<template> <div> <button h-onclick="echo event.MouseEvent.x" h-onmouseover="echo event.MouseEvent.x"> Click! </button> <input placeholder="edit text ..." h-oninput="echo event.target.value" /> </div> </template>
Заключение
Императивный подход к разработке в HappyX все еще находится в разработке, однако уже предоставляет некоторый функционал, в том числе маршрутизацию, директивы для отрисовки элементов, компонентный подход а также обработку событий. При этом Nim используется как основной язык для разработки.
Ссылки
Прошлая статья о сравнении HappyX и Karax.
GitHub репозиторий.
Официальный сайт с документацией, написанный на HappyX.
