В данном посте, мы рассмотрим два новых инструмента из мира фронт-енд разработки. Они оба разработаны с мыслью о простоте и легкости использования. Первый инструмент это очень маленький фронт-енд фреймворк Hyperapp, а второй это бандлер Parcel. Вместе они могут быть лучшим выбором для написания легковесных приложений в начале 2018.

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

Hyperapp


Главная особенность Hyperapp — это его размер, всего 1kb. Пользователи React & Redux, будут чувствовать себя как дома, они очень похожи с Hyperapp.

image
Hyperapp похож на крошечную версию React Рика
Hyperapp использует систему Virtual DOM, для вычислений обновлений DOM, а так же преследует схожу абстракцию компонентов и элементов поверх HTML, что позволяет использовать JSX. Ключевое отличие заключается в том компоненты не имеют state — есть только один глобальный state на все приложение и все компоненты представлены как stateless.

State менеджмент в Hyperapp вдохновлен подходом Elm, который в свое время вдохновил Redux.
Концепция имьютбл стейта и экшенов которые возвращают новый стейт очень похожа на ту, что используется в Redux. Однако в Hyperapp нету редюсеров, есть только экшены, которые принимают аргументы и возвращают новый стейт (асинхронные экшены возвращающие промисы, так же присутствуют).

Parcel


Parcel это новый вид фронт-енд бандлера, своего рода более быстрый и пре-сконфигурированный webpack. Я фанат webpack'a, но как и любой другой, могу заметить, что конфигурация webpack'a довольно громоздкая.

Parcel из коробки предоставляет HOC (hot module replacement) и code-splitting. К моему удивлению, заимпортив .sass файл, после запуска дев сервера parcel, он автоматически установил node-sass в зависимости и собрал мне CSS файл! Также стоит отметить, что parcel нереально быстрый.

И так, как же я использую эти два инструмента?


Мы напишем приложение с использованием Hyperapp & Parcel — очень простое, оно будет просто отображать базовую информацию о пользователе используя Github API.

Линк на готовое решение

Давайте начнем с создания новой директории и установки необходимых зависимостей:

mkdir hyperparcel && cd $_ && npm init -y && npm i hyperapp parcel-bundler babel-plugin-transform-react-jsx babel-preset-env

Наряду с главными инструментами, что нас интересует, мы установили babel пресеты для траспилинга кода под старые окружения.
$_ означает «аргумент последней команды»

Теперь давайте добавим index.html и index.js файлы:
index.html

<html>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

index.js

console.log('hello parcel')

Так же давайте добавим скрипты запуска дев сервера и билда в package.json:

...
"start": "parcel index.html",
"build": "parcel build index.html --public-url ./"
...

Запустите дев сервер $ npm start по адресу localhost:1234 в браузере будет доступна наша страница и в консоле можно увидеть результат console.log из index.js.

Окей, теперь Hyperapp приложение. Для упрощения, мы будем использовать ES6 и JSX — используя babel компилятор. К счастью он есть в Parcel по умолчанию и все, что нам необходимо это указать env пресет и компиляцию JSX под Hyperapp. (Вместо React.createElement, должна вызываться функция h).

Создайте файл .babelrc в корне проекта:

{
  "presets": ["env"],
  "plugins": [
    [
      "transform-react-jsx",
      {
        "pragma": "h"
      }
    ]
  ]
}

К этому моменту мы закончили настройку нашего окружения и можем приступить к написанию приложения. Давайте начнем с простого Hyperapp представления:

import { h, app } from 'hyperapp'
const view = () =>
  <div>
    hello hyperapp
  </div>
app({}, {}, view, document.body)

Поскольку Parсel использует hot релоады, вы сразу же увидите результат.
Код очень простой и не требует каких либо глубоких познаний Hyperapp.

Мы импортируем необходимое из Hyperapp, создаем view функцию которая отвечает за отображение контента и рендерит все это в body страницы используя app функцию.

Теперь займемся вытяжкой Github информации о пользователе. В первую очередь нам нужно некое состояние для того, чтобы хранить пользовательский ввод и данные полученные с API. (Как было сказано в начале, компонент в Hyperapp не имеет своего сайта).

const state = {
  username: '',
  userData: null,
}

Также нам необходим способ изменить этот стейт. Изменения стейта в Hyperapp похожи на то как это делается в Elm и Redux и представлены в виде функций которые принимают стейт с экшеном и возвращают новый стейт:

const nameChangeAction = (name) => (state, actions) => ({...state, name})

В нашем случае, экшены будут выглядить так:

const actions = {
  updateUsername: (username) => (state, actions) => {
    getUserData(username).then(actions.setUserData)
    return { username }
  },
  setUserData: userData => state => ({ userData })
}

Как вы можете видеть updateUsername асинхронный и использует другой экшен для изменения стейта.

Разумеется нам понадобится функция getUserData. Мы будем использовать Github API для получения информации Github аккаунте ник которого ввел пользователь.

Для того, что бы не дергать API на каждый символ, мы будем использовать debounce.

npm i debounce-promise babel-preset-es2015

const getUserDataFn = username => {
  return fetch(`https://api.github.com/users/${username}`)
    .then(res => res.json())
}
const getUserData = debounce(getUserDataFn, 700)

Создайте файл стилей (можно взять туть) .sass и испортируете его в index.js

А так же обновите view:

const view = (state, actions) =>
  <main>
    <div>Search github users:</div>
    <input
      type='text'
      className='searchInput'
      value={state.username}
      oninput={e => actions.updateUsername(e.target.value)}
    />
    <br/>
    <div className='userCard'>
      {state.userData ? (
        <div>
          <img class='userCard__img' src={state.userData.avatar_url} />
          <div class='userCard__name'>{state.userData.name}</div>
          <div class='userCard__location'>{state.userData.location}</div>
        </div>
      ) : (
        <div> search 'em</div>
      )}
    </div>
  </main>
app(state, actions, view, document.body)

Вот и все! Наше маленькое приложение готово.

В заключение


Hyperapp и Parcel отличный пример того как инструментарий разработчика эволюционирует. Существует множество идей и подходов создания фронт-енд приложений с богатым интерактивным интерфейсом. Некоторые из них быстро адаптируются под новые реалии и нужды, а некоторые из них будут оставлены позади в этом дивном мире Javascript разработки. React & Redux является очень популярным выбором среди разработчиков и их ключевые особенности используются в Hyperapp. Webpack в свою очередь повлиял на мир бандлеров и установил новые стандарты которые Parcel удовлетворяет с лихвой и кто знает, возможно в будущем он займет лидирующую позицию. Эволюция так же является причиной по которой мы не должны пытаться использовать один и тот же набор инструментов для решения каждой новой проблемы.

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


  1. Dreyk
    11.02.2018 12:01

    я понимаю, что это перевод, но для справедливости отмечу, что webpack 4 (который сейчас в бете) тоже работает вообще без конфигурации


    1. auine Автор
      11.02.2018 12:26

      Он даже sass понимаем, и всякое такое?


      1. AndreyRubankov
        11.02.2018 13:35

        Тут сразу встает логичный вопрос: а стоит ли простому сборщику проектов знать про конкретные инструменты?


        1. auine Автор
          11.02.2018 13:42

          Учитывая, что такие вещи как sass (опять же пример из статьи) являются широко используемыми, то почему бы и нет? Удобно. Понятное дело, что все автоматизировать нельзя и есть большое количество различных кейсов, но так или иначе, глядя на те же сурвеи, можно составить конечный список расширений и тулзов используемых в большинстве проектов. Хотя надо еще глянуть как они это делают, мб там, что-то похитрей :)


          1. AndreyRubankov
            11.02.2018 23:58

            то почему бы и нет? Удобно.
            На первый взгляд – да, крутая идея и очень удобно!
            Но отсутствие контроля за сборкой – это больше проблема, чем удобство. Неизвестно когда, что и как будет собираться. И как эту сборку отключить? (к примеру в целях отладки)

            ну и есть еще другие интересные вопросы:
            Сколько популярных вещей есть в js мире?
            Как много кода для поддержки их всех нужно добавить в сборщик?
            Как они все будут между собою взаимодействовать?
            Как долго будет тянуться поддержка «легаси» версий, чтобы никому ничего не сломать?


            1. auine Автор
              12.02.2018 00:42

              Да это не легко :)
              Вот, если интересно, немного информации https://parceljs.org/how_it_works.html


  1. Dimensi
    11.02.2018 13:34

    По-моему hyperapp просто игрушка, которую хватит только на виджеты, чтоб сделать серьезное приложение на нем, легче вены вскрыть. У автора библиотеки даже нет представлений как сделать «глобальный» стейт и в ишью, где задавали вопрос, автор просто выбрал более менее адекватные варианты и выложил их как «решения».


    1. auine Автор
      11.02.2018 13:36

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


  1. zim32
    11.02.2018 20:08

    Версия 1.1.2. Думаю уже устарел


  1. chuikoffru
    13.02.2018 00:57

    Не нашел примеров, как разбивать на более мелкие компоненты, и вставлять их в родительские. Где можно посмотреть такую реализацию?


    1. auine Автор
      13.02.2018 01:30

      Точно так же как и в React :)
      С гитхаба примерчик github.com/hyperapp/hyperapp
      Box это тоже какой-то компонент

      const HelloBox = ({ name }) => (
        <Box color="green">
          <h1 class="title">Hello, {name}!</h1>
        </Box>
      )