OpenSource на завтрак

от автора

Делиться своими идеями с сообществом — хорошо и полезно. Это позволяет развиваться, перенимать лучшие практики, исследовать новые инструменты, учиться оформлять свои решения. Но какой код стоит выносить в общий доступ? И как делать это на постоянной основе? Чтобы разобраться в этих вопросах, я решил сделать свой JavaScript OpenSource Boilerplate — маленькую, но максимально расширяемую библиотеку компонентов. Я назвал её handy-ones.

Задача

На самом деле задача формулируется даже шире, основной мотив в любом случае — исследовательский. Каждый день в JavaScript-мире появляются новые инструменты и технологии, противостояние ui-фреймворков сменилось борьбой js-рантаймов, бандлеров, стейт-менеджеров, утилит для работы с монорепозиториями. Мне понадобился проект, внутри которого я смогу экспериментировать с новыми технологиями, а удачные эксперименты легко оформлять и публиковать. Я сформулировал к нему такие требования:

  1. Удобство и комфорт разработки. Проект должен запускаться и собираться быстро, должны быть доступны все возможные инструменты: HMR, source-maps и тд.

  2. Удобство для потребителя. Компиляция в ES6, d.ts и source-maps внутри пакетов. Поставка через npm.

  3. Минимальный time-to-market. От написания кода до дистрибуции должны проходить минуты.

  4. Независимые сборка, поставка, тестирование отдельных компонентов системы. Кто знает, что нового придумают завтра, и в чем захочется поковыряться?

  5. Минимальная конфигурация и максимальная унификация инструментов.

  6. Документация и demo-стенды «из коробки».

Инструменты

Технологический стек, позволивший решить большинство этих задач, выглядит следующим образом:

  1. npm для установки внешних и перелинковки локальных зависимостей

  2. turborepo для запуска скриптов

  3. tsc и postcss-cli для сборки пакетов

  4. vite для сборки сервисных бандлов

  5. ladle как движок для демо-стенда

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

First ones

Первые компоненты — самые простые, базовые, атомарные. Но они же и самые универсальные и гибкие. Объединяет их еще и стремление решить задачу максимально нативным способом: взять все лучшее, что предлагает браузерное API, и написать поверх него минимальное количество JavaScript-кода.

Каждый из этих компонентов доступен через npm, но часто значимый код можно забрать прямо из гитхаба.

Всего таких компонентов четыре:

  1. handy-clamp — React-компонент, умеющий прятать лишние строчки текста под «кат»;

  2. handy-range-slider — полностью кастомизируемый, но в тоже время нативный компонент выбора диапазона;

  3. handy-scroll-strip — надстройка над браузерным скролл-баром, позволяющая изящно размещать на странице длинные горизонтальные блоки контента;

  4. handy-svg — самый удобный способ встраивания SVG в вебе. Я уже рассказывал о нем на Хабре.

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

Расскажу о первой пачке компонентов подробнее.

Handy clamp

Демо | Исходники

handy-clamp отображает первые две строчки текста
handy-clamp отображает первые две строчки текста

Типовая задача — спрятать лишний текст под «кат». Особенность handy-clamp в том, что он умеет обрезать текст по строчкам, а еще он отзывчивый и максимально нативный. В основе его работы две технологии:

  1. Так и не ставшее стандартом, но поддержанное всеми современными браузерами css-свойство line-clamp

  2. ResizeObserver — браузерное API для отслеживания изменений размера элемента.

Дальше все просто — следим за размерами элемента с текстом, проверяем, помещается ли он в своего родителя:

const isOverflowing = el.scrollHeight > el.clientHeight

Если не помещается — рисуем кнопку «Показать еще», если помещается — скрываем эту же кнопку. Заворачиваем идею в React-компонент, придумываем незамысловатое API.

import {HandyClamp} from '@handy-ones/handy-clamp';  <HandyClamp lines={2}>    Длинный-длинный текст в десятки строчек, который надо спрятать... </HandyClamp>

Публикуем, любуемся, ставим чайник, двигаемся дальше.

Handy range slider

Демо | Исходники

handy-range-slider можно раскрашивать
handy-range-slider можно раскрашивать

Еще со времен jQuery я работал со множеством разных компонентов выбора диапазона, среди них слайдер из jQuery UI, замечательное standalone-решение noUiSlider, React-обертки в Material UI и Ant Design. И я знаю про опыт ребят из Тинькофф с кастомизацией нативного контрола range-инпута, это и правда впечатляющая работа. Но тащить в проект тяжелое решение снаружи не хотелось, а подход с css-кастомизацией нативного контрола не дает полной гибкости. Например, не получится покрасить трек слайдера градиентом. Плюс, css-магия вокруг нативного компонента меня немного пугает, я не очень понимаю, как поддерживать «три слоя градиента» и пачку вендорных префиксов.

Для горизонтального слайдера с одним ползунком я решил использовать нативный браузерный компонент, а для гибкой стилизации использовать элементы, отрендеренные через JavaScript. Для меня лучше всего сработал следующий подход:

  1. Рендерим нативный слайдер нужного размера. И делаем все его элементы полностью прозрачными.

  2. Рендерим под ним псевдо-контрол, любую удобную нам HTML-разметку, которая будет отвечать исключительно за отображение. Стилизуем ее как нам нравится.

  3. Связываем нативный и псевдо контролы через локальный стейт. Тут нужно будет чуть-чуть арифметики и внимательно потестировать в разных браузерах.

В итоге пользователь управляет нативным контролом, а отображается расположенный слоем ниже псевдо-контрол, кастомизация которого никак не ограничена. И это работает, и это просто, и это мало кода, и это mobile-friendly.

Дальше — дело техники, тестируем в разных браузерах, придумываем API, способ отрисовки лейблов, оформляем код:

import {HandyRangeSlider} from '@handy-ones/handy-range-slider';  <HandyRangeSlider     min={50}     max={100}     step={2}     value={10}     labels={[         {value: 50, text: '?'},         {value: 75, text: '?'},         {value: 100, text: '❤️'}     ]}     onChange={(event, parsedValue) => {}} />

Проектировать АПИ у инпутов на самом деле очень просто, любые инпуты должны управляться всего двумя пропсами: value и onChange. А первым аргументом в onChange всегда должен приходить оригинальный event. Этот подход использует React, а также самые популярные библиотеки для работы с формами: Formik, React Hook Form, React Final Form.

Текущее решение работает во всех современных десктопных и мобильных браузерах, а также в IE 10+, если это по какой-то причине еще важно. Нет никаких проблем адаптировать handy-range-slider для вертикального отображения, но Firefox все еще не полностью его поддерживает.

Так что публикуем то, что есть, закидываем пару примеров на демо-стенд, чайник как раз вскипел.

Handy Scroll Strip

Демо | Исходники

handy-scroll-strip изящно прячет лишний контент за горизонтальной прокруткой
handy-scroll-strip изящно прячет лишний контент за горизонтальной прокруткой

Простейший, но в то же время универсальнейший компонент: полоса прокрутки без браузерных контролов, но с фейдом по краям и императивным API. Я создавал на его основе галереи, мобильную навигацию, прятал в него огромные multistep-формы и широченные таблицы. Он очень производительный, ведь скролл в нем нативный. Хорошей идеей будет также его использование с css-scroll-step.

В реализации он очень прост:

  1. Скрываем полосы прокрутки

  2. Добавляем блоки с градиентом по краям, слушаем скролл, отображаем их и прячем.

  3. Реализуем императивное API, с ужасом продираясь через useImperativeHandle и вспоминая, как удобно это было реализовано в классовых, а не функциональных компонентах.

Скрыть полосы прокрутки можно через css, это кроссбраузерно, хотя и многословно:

.content {     overflow-x: scroll;     overflow-y: hidden;     -webkit-overflow-scrolling: touch;     -ms-overflow-style: none;  /* Internet Explorer 10+ */     scrollbar-width: none;  /* Firefox */ }  .content::-webkit-scrollbar {     display: none;  /* Safari and Chrome */ }

В императивном API можно использовать нативный браузерный Element.scrollTo(), он уже давно умеет скроллить плавно:

element.scrollTo({     left: 2000,     behavior: 'smooth' });

Вот и всё, основные сложности тут будут с типизацией императивного API и forwardRef, но есть хороший пример в React Typescript Cheatsheet, интерфейс получается такой:

interface ImperativeHandlers {     getContainer: () => HTMLDivElement | null;     getScrollLeft: () => number;     scrollTo: (value: number) => void; }

Решить остальные проблемы поможет наш бойлерплейт. Закидываем в отдельную папку, публикуем и проверяем, что там осталось в холодильнике.

Handy SVG

Демо | Исходники

SVG-изображения, встроенные через handy-svg, можно красить в css
SVG-изображения, встроенные через handy-svg, можно красить в css

Не буду подробно останавливаться на этой утилите, про нее у меня была отдельная статья на Хабре. Напомню только, что с ее помощью можно динамически встраивать SVG-изображения в web-проекты с любым технологическим стеком. Под капотом она делает fetch-запросы для получения кода SVG-изображений, формирует на странице спрайт символов и предоставляет React и standalone API для отрисовки:

import {HandySvg} from '@handy-ones/handy-svg'; import iconSrc from './icon.svg';  export const Icon = () => (     <HandySvg         src={iconSrc}         className="icon"         width="32"         height="32"     /> );

Утилита handy-svg какое-то время существовала как независимо решение, но сделать ее частью библиотеки handy-ones не составит труда. Благодаря изолированности отдельных проектов внутри монорепозитория не придется даже синхронизировать зависимости старых и новых библиотек, npm хорошо справляется с этим сам. Порядок действий такой же, как с новыми компонентами: новая папка, новый package.json, публикуем и выдыхаем. На сегодня кажется хватит.

Boilerplate

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

Мой совет — не надо долго пыхтеть и вымучивать что-то заумное, большинство хороших решений простые. Гораздо важнее принцип fail-fast — оформите свое решение, поделитесь им, выслушайте критику, двигайтесь дальше. Даже стендап-комики с многолетним опытом не понимают, смешная ли шутка, пока не расскажут ее перед аудиторией*. Важно делать легко и просто, как щелкать пальцами, тогда шансы сделать что-то по-настоящему полезное сильно возрастают.

Вопросы, комментарии а также пул-реквесты всячески приветствуются.

Я буду развивать handy-ones по мере возможностей, за завтраком и в дороге, и в любое удобное время, главное, что теперь у меня есть отличный инструмент для этого. Как раз про него, а также про то, почему я не стал использовать lerna, tsup, esbuild, storybook, yarn и всерьез подумывал про gulp, я расскажу в следующей статье.


* Видео, на котором Луи Си Кей рассказывает про это, удалили с Ютуба, про него немного помнит reddit и, я надеюсь, кто-то из читателей.


ссылка на оригинал статьи https://habr.com/ru/post/694510/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *