NemerleWeb — Уникальный веб-фреймворк

в 18:57, , рубрики: .net, javascript, nemerle, signalr, Веб-разработка

NemerleWeb NemerleWeb — это фреймворк для создания одностраничных веб приложений (Single Page Application — SPA), который транслирует код написанный на Nemerle в смесь JavaScript и HTML, а также обеспечивает двустороннюю привязку данных, прозрачное дуплексное общение с сервером, статическую типизацию с настоящими подсказками и ещё много чего другого.

Как это работает?

Разработчик описывает логику моделей на компилируемом языке Nemerle.
Благодаря макросам, код на этом языке получается очень лаконичным, фактически мы только описываем логику приложения. Это выгодно отличает наше решение от библиотек на чистом JavaScript.

Большинство моделей содержат в себе один или больше HTML шаблонов, с которыми осуществляется двухсторонняя привязка данных. Привязка, как и весь остальной код, выглядит очень просто:

<input value=”$Name” />

Достаточно внутри атрибута поставить первым символом $, и атрибут будет вычисляться автоматически, на основании последующего кода.
В этом коде, кстати, разрешены практически любые выражения — главное, чтобы эти выражения уместились в результирующем HTML.

Дальше из созданной модели генерируются *.js файлы и шаблоны, и всё это дело вставляется в вашу статичную до того страницу.

Описание модели — это один файл внутри обыкновенного ASP.NET MVC проекта.

Покажите мне код!

Код простейшей модели выглядит примерно так:

[Unit]
public class Page
{
  Name : string = "world";

  [Html]
  public View() : string
  {
   <#
     <input value=”$Name” /> 
     <div>Hello, $Name</div>
   #>
  }
}

Смотреть: Результат, Исходный код

Из этого будет сгенерирован JavaScript “класс” Page и HTML шаблон Page_View, который включает в себя примитивы для автоматической двусторонней привязки данных.

Атрибутом [Unit] помечаются классы, из которых нужно создавать client side код, а [Html] те методы, которые мы хотим превратить в шаблоны.

У меня уже есть готовый сайт на C#

Если у вас есть готовый сайт, к которому не хочется приплетать Nemerle, вы можете создать отдельный проект на Nemerle, модель из которого вставить в основной сайт на C# таким образом:

public ActionResult PageWithModel() 
{
  return View("Index", (object)NemerleProject.MyModel.Render());
}

Статический метод Render будет автоматически сгенерирован компилятором для любой модели, которую вы создадите. В этот метод, кстати, можно передавать параметры, с которыми потом будет вызван конструктор на стороне клиента.

Почему Nemerle и NemerleWeb?

Дело в том, что Nemerle обладает очень развитой инфрастуктурой для создания макросов. Разработчику позволено вмешиваться во многие стадии компиляции и по необходимости вносить свои изменения.
Именно метапрограммирование позволило создать фреймворк с очень лаконичной структурой и с практически неограниченными возможностями. А так как Nemerle к тому же статически типизирован, мы бесплатно получаем интеллисенс и базовую проверку на опечатки.

Список задач

Для демонстрации возможностей я выбрал типичный список задач (ToDo list), но с расширенными возможностями. Наш список будет автоматически сохранять изменения на сервер, при этом оповещая других клиентов об обновлении посредством SignalR.

Задачи будут отсортированы по приоритету с помощью LINQ. Им, кстати, можно пользоваться прямо из шаблона.

Для общения сервера с клиентом мы используем SignalR, который уже является стандартом для подобного рода решений в ASP.NET.

// Вся логика в классе с атрибутом [Unit] будет транслирована в JavaScript, 
// кроме внутреннего класса Server, из которого будет сгенерирован
// ASP.NET MVC Controller
[Unit]
public class ReactiveToDo
{
  // Макрос [Dto] помечает все поля как public mutable.
  // Также добавляет макроатрибут [Record], который создаёт конструктор,
  // принимающий значения всех полей
  [Dto] public class Task { Name : string; IsDone : bool; Priority : string; }
  // Все переменные в типе с атрибутом [Unit] находятся на клиенте за исключением переменных класса Server, которые будут на сервере.

  // В Nemerle по умолчанию все поля неизменяемые в отличии от C#.
  // Нам нужны изменяемые поля, поэтому мы помечаем их модификатором mutable.
  mutable _tasks = List.[Task]();
  mutable _todoName = "New task";
  mutable _todoPriority = "high"; 

  public this() 
  {
    // Поле server генерируется автоматически, если в классе Unit
    // присутствует класс Server (см. ниже)
    // К параметрам метода Load() добавляется callback,
    // который принимает результат.
    // Так как параметров у Load нет, то callback - это единственный параметр.
    // Методы сервера возвращают объект типа XMLHttpRequest
    // Благодаря этому возможно узнать статус запроса и отменить вызов.
    _ = server.Load(tasks => SetTasks(tasks));
  } 

  // Мы будем вызывать этот код с сервера с помощью SignalR,
  // поэтому выделяем такую простую логику в отдельный метод
  SetTasks(tasks : List[Task]) : void
  { 
    _tasks = tasks; 
  } 

  Add() : void
  {
    _tasks.Add(Task(_todoName, false, _todoPriority));

    SaveToServer(); 

    _todoName = "Task #" + _tasks.Count;
    _todoPriority = "high";
  }

  SaveToServer() : void
  {
    // Метод Save принимает параметр List[Task], к которому автоматически генерируется 
    // функция обратного вызова с результатом.
    // На данный момент нам с этим результатом делать нечего, 
    // поэтому просто выведем его в консоль, для наглядности
    // Здесь window.console.log это обычный вызов JavaScript функции.
    _ = server.Save(_tasks, status => window.console.log(status)) 
  } 

  // О примитивах привязки данных мы расскажем впоследствие более подробно.
  // Можете обратить внимание, что внутри шаблонов работает LINQ, 
  // который реализован с помощью linq.js
  // <# #> - аналог C# строк вида @””, но позволяет писать обычные кавычки.
  // Также эти строки поддерживают рекурсию: <# <# a #> #> .
  [Html] 
  public View() : string
  {
   <#
     <table class="reactive-todo-table">
       <tr>
         <th>Priority</th><th>Task</th><th>Status</th>
       </tr>
       <tr $foreach(task in _tasks.OrderBy(t => t.Priority))>
         <td>$(task.Priority)</td>
         <td>$(task.Name)</td>
         <td><input type="checkbox" event-change="$SaveToServer" checked="$(task.IsDone)" /></td>
       </tr>
     </table>
     <div>
       <input value="$_todoName" />
       <select value="$_todoPriority">
         <option>high</option>
         <option>low</option>
       </select>
       <button click="$Add">Add</button>
     </div>
   #>
  } 

  // Из этого класса будет сгенерирован контроллер ASP.NET MVC
  // Весь маппинг между клиентом и сервером делается автоматически,
  // поэтому параметры и возвращаемое значение это те же типы, которые
  // мы используем на стороне клиента.
  // Атрибутом [SignalR] помечаются те типы Server,
  // в которых мы будем пользоваться макросами broadcast или signal (см. ниже)
  [SignalR]
  public class Server
  { 
    // Эта переменная находится на сервере.
    // Статическая переменная дает нам в некотором роде БД в памяти.
    static mutable _db : List[Task] = List();

    static this()
    {
      _db.Add(Task("Write article", false, "high"));
      _db.Add(Task("Fix website bugs", false, "high"));
      _db.Add(Task("Add new functionality", false, "low"));
    }  

    public Load() : List[Task]
    {
      // Nemerle позволяет не писать return для возвращения значения
      // Такой синтаксис позволяет реализовать идею "Все есть выражение"
      _db 
    }

    // Методы сервера всегда возвращают значение.
    // В будущем будет позволенно объявить метод возвращающий ‘void’.
    public Save(tasks : List[Task]) : string
    {
      _db = tasks;

      // Вот он весь SignalR. Как обычно, все неудобные подробности 
      // для нас генерирует макрос. Мы же просто пользуемся готовым
      // полем client, у которого есть все методы присутствующие на 
      // стороне клиента.
      // Опять же маппинг автоматический.
      // Макрос broadcast вызовет метод SetTasks у всех клиентов. 
      // В противопоставление ему есть макрос signal, который вызовет
      // метод только у текущего пользователя и ни у кого более

      broadcast client.SetTasks(_db);

      //Возвращать нам нечего, поэтому просто отвечаем:
      "ok"
    }
  }
}

Смотреть: Результат, Исходный код
Пример, очищенный от коментариев: gist

Примечание: Откройте страницу в двух вкладках одновременно, чтобы увидеть как данные обновляются автоматически.

Отступление

Цель данной публикации — не сподвигнуть людей использовать NemerleWeb в продакшене, а скорее рассказать о проекте и, может быть, найти единомышленников. По нашему скромному мнению, макросистема Немерле позволяет делать вещи, которые раньше просто не были доступны в веб разработке, поэтому мы и увлеклись этой затеей. Очень надеемся, что в конце концов из этого можно будет сделать стройный надёжный фреймворк.

Заключение

На этом первую статью пора завершать.

В следующих частях расскажем про:

  • Привязка данных, шаблоны.
  • Связь с сервером, SignalR
  • Подробности трансляции, Typescript (да, мы умеем автоматически парсить декларации Typescript)

Ссылки

Сайт проекта: www.nemerleweb.com
Репозиторий: https://github.com/NemerleWeb/NemerleWeb

Автор: Dale

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js