Хотите узнать, как с помощью Nim создавать клиентские веб-приложения?

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

Обложка веб-фреймворка HappyX
Обложка веб-фреймворка HappyX
Обложка Karax
Обложка Karax

Начну с того, что в экосистеме 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.

Комментарии (4)


  1. gohrytt
    02.12.2023 17:46

    Пробовал HappyX, вдохновился бенчмарками тек-эпауэр и the-benchmarker, но натолкнулся на какую-то ну очень отвратительную производительность и недружелюбность либы к программисту. Возможно дело в том, что коврялся в этом без языкового сервера, но тем не менее пока не вдохновляет.


  1. kesn
    02.12.2023 17:46
    -1

    Совершенно не понимаю, почему просто не прикрутить какой-нибудь шаблонизатор. HTML, смешанный с языковыми конструкциями, выглядит ужасно. Получается какой-то php


    1. inv2004
      02.12.2023 17:46

      Это надо предъявить react в том числе :)


  1. inv2004
    02.12.2023 17:46
    +2

    Примеры на karax выглядят аккуратнее, хотя happyx кажется более рекламируемым