Эта статья — перевод оригинальной статьи Luca Casonato "Fresh 1.0"

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

Fresh — это новый полнофункциональный веб-фреймворк для Deno. По умолчанию веб-страницы, созданные с помощью Fresh, не отправляют никакого JavaScript в браузер. Фреймворк не имеет шага сборки, что позволяет на порядок сократить время развертывания. Сегодня мы выпускаем первую стабильную версию Fresh.

В последние годы рендеринг на стороне клиента становится все более популярным. Страницы React (и подобные React) позволяют программам относительно легко создавать очень сложные пользовательские интерфейсы. Популярность проявляется в текущем пространстве веб-фреймворков: в нем преобладают фреймворки на основе React.

Но рендеринг на стороне клиента стоит дорого; фреймворк часто отправляет пользователям сотни килобайт клиентского JavaScript по каждому запросу. Эти пакеты JS часто делают немногим больше, чем рендеринг статического контента, который с таким же успехом можно было бы использовать как обычный HTML.

Некоторые новые фреймворки также поддерживают рендеринг на стороне сервера. Это помогает сократить время загрузки страницы за счет предварительного рендеринга на сервере. Но большинство современных реализаций по-прежнему предоставляют каждому клиенту всю инфраструктуру рендеринга для полного приложения, поэтому страница может быть полностью повторно отрендерена на клиенте.

Это плохой подход к разработке — клиентский JavaScript очень дорог: он замедляет работу пользователя, резко увеличивает энергопотребление на мобильных устройствах и часто не очень надежен.

Fresh использует другую модель: по умолчанию вы отправляете клиентам 0 КБ JS. Таким образом большая часть рендеринга выполняется на сервере, а клиент отвечает только за повторный рендеринг небольших островков интерактивности. Модель, в которой разработчик явно соглашается на отрисовку определенных компонентов на стороне клиента. Эта модель была описана еще в 2020 году Джейсоном Миллером в его сообщении в блоге Islands Architecture.

По своей сути Fresh представляет собой структуру маршрутизации и механизм шаблонов, который отображает страницы по мере их запроса на сервере. В дополнение к JIT-рендерингу на сервере Fresh также предоставляет интерфейс для плавного рендеринга некоторых компонентов на клиенте для максимальной интерактивности. Фреймворк использует Preact и JSX (или TSX) для рендеринга и создания шаблонов как на сервере, так и на клиенте. Клиентский рендеринг полностью включен на уровне компонентов, и поэтому многие приложения вообще не отправляют JavaScript клиенту.

Fresh не имеет шага сборки. Код, который вы пишете, является непосредственно кодом, который выполняется на стороне сервера и на стороне клиента, и любая необходимая транспиляция TypeScript или JSX в простой JavaScript выполняется вовремя, когда это необходимо. Это позволяет создавать очень быстрые циклы итераций с мгновенным развертыванием.

Быстрый старт

Чтобы действительно объяснить, что делает Fresh особенным, давайте создадим новый проект и посмотрим на код:

deno run -A -r https://fresh.deno.dev my-app

Эта команда создает минимальный шаблон, необходимый для проекта Fresh, в папке, которую вы указали в качестве последнего аргумента (в данном случае my-app). Вы можете узнать больше о том, что означают все файлы, в руководстве по началу работы.

my-app/
├── README.md
├── deno.json
├── dev.ts
├── fresh.gen.ts
├── import_map.json
├── islands
│   └── Counter.tsx
├── main.ts
├── routes
│   ├── [name].tsx
│   ├── api
│   │   └── joke.ts
│   └── index.tsx
└── static
    ├── favicon.ico
    └── logo.svg

А пока обратите внимание на папку route/. Он содержит обработчики и шаблоны для каждого маршрута приложения. Имя каждого файла определяет, каким путям соответствует маршрут. Например, файл api/joke.ts обслуживает запросы к /api/joke. Структура папок может напомнить вам Next.js или PHP, так как эти системы также используют маршрутизацию файловой системы.

Давайте посмотрим на файл route/index.tsx:

/** @jsx h */
import { h } from "preact";
import Counter from "../islands/Counter.tsx";

export default function Home() {
  return (
    <div>
      <img
        src="/logo.svg"
        height="100px"
        alt="the fresh logo: a sliced lemon dripping with juice"
      />
      <p>
        Welcome to `fresh`. Try update this message in the ./routes/index.tsx
        file, and refresh.
      </p>
      <Counter start={3} />
    </div>
  );
}

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

Это ставит вопрос: что, если вы хотите повторно отобразить некоторые части приложения на клиенте, например, в ответ на какое-либо взаимодействие с пользователем? Для этого и существуют острова в Fresh. Это отдельные компоненты приложения, которые повторно гидратируются на клиенте для обеспечения взаимодействия.

Ниже приведен пример острова, который предоставляет счетчик на стороне клиента с кнопками увеличения и уменьшения. Он использует хук Preact useState для отслеживания значения счетчика.

// islands/Counter.tsx

/** @jsx h */
import { h } from "preact";
import { useState } from "preact/hooks";

interface CounterProps {
  start: number;
}

export default function Counter(props: CounterProps) {
  const [count, setCount] = useState(props.start);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

Острова должны быть помещены в папку islands/. Fresh позаботится об автоматическом повторном гидратации острова на клиенте, если обнаружит его использование в шаблоне маршрута.

Fresh — это не просто интерфейсный фреймворк, а полностью интегрированная система для написания веб-сайтов. Вы можете произвольно обрабатывать запросы любого типа, возвращать настраиваемые ответы, делать запросы к базе данных и многое другое. Этот маршрут возвращает обычный текстовый HTTP-ответ вместо HTML-страницы, например:

// routes/api/joke.ts

const JOKES = [/** jokes here */];

export const handler = (_req: Request): Response => {
  const randomIndex = Math.floor(Math.random() * JOKES.length);
  const body = JOKES[randomIndex];
  return new Response(body);
};

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

// routes/blog/[id].tsx

import { HandlerContext, PageProps } from "$fresh/server.ts";

export const handler = async (_req: Request, ctx: HandlerContext): Response => {
  const body = await Deno.readTextFile(`./posts/${ctx.params.id}.md`);
  return ctx.render({ body });
};

export default function BlogPostPage(props: PageProps) {
  const { body } = props.data;
  // ...
}

Поскольку Fresh так сильно зависит от динамического рендеринга на стороне сервера, крайне важно, чтобы он был быстрым. Это делает Fresh очень подходящим для работы на периферии в средах выполнения, таких как Deno Deploy, Netlify Edge Functions или Supabase Edge Functions. Это приводит к тому, что рендеринг происходит физически рядом с пользователем, что минимизирует задержку в сети.

Развертывание приложения Fresh в Deno Deploy занимает всего пару секунд: отправьте код в репозиторий GitHub, затем свяжите этот репозиторий с проектом на панели инструментов Deno Deploy. Затем ваш проект будет доступен из 33 регионов по всему миру, при этом 100 000 запросов в день включены в уровень бесплатного пользования.

Production Ready

Fresh 1.0 — это стабильная версия, на которую можно положиться при использовании в продакшене. Многие общедоступные веб-сервисы Deno используют Fresh. Это не означает, что мы закончили разработку, у нас есть еще много идей по улучшению взаимодействия с пользователями и разработчиками.

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


  1. Daniil_Palii
    30.06.2022 18:22

    Кто разбирается в .NET, правильно ли я понимаю, что подход к рендеру здесь такой же, как в Blazor Server?