Каждый раз начиная писать React приложение, вы так или иначе выберите какой-то вариант:
- копи-паст вашего предыдущего проекта
- какой-то бойлерплейт или даже генератор (типа Yeoman)
- готовый фреймворк не требующий конфигурации
- пишете сами все с нуля
Каждый из способов имеет свои сильные и слабые стороны, как на длинной, так и на короткой дистанции.
Некоторые решения скрывают сложность в начале, позволяя сделать быстрый старт. Это что-то вроде решения под ключ, но в результате такие решения могут оказаться недостаточно гибкими и сложными в подстройке. С другой стороны, в начале все может казаться слегка монструозным и неповоротливым, и чтоб начать нужно немного повозиться, но зато потом преимущества станут очевидными. Всегда есть возможность сделать все с нуля, ровно так, как хочется, но в таком случае Вы будете отвечать за бесчисленные аспекты и Вам потребуются очень глубокие знания во всех участвующих технологиях.
Очевидно, что иметь некий стандарт внутри компании крайне полезно, когда речь идет о разработке нескольких проектов, таким образом любая команда может быть переключена на любой проект. Или в случае дизайн-студии, которая постоянно создает новые и новые проекты. В этом случае создание основы должно занимать как можно меньше времени. Общий стандартный подход позволяет командам и разработчикам делиться знаниями, можно вести некую базу стандартных решений, лучших практик и т.д.
Анализ рынка показывает, что существует несколько довольно стабильных проектов, предоставляющих некую инфраструктуру для создания сайтов на React. Я выбрал несколько из них, стараясь чтоб все кандидаты, для наглядности, имели как можно более разную природу.
Кандидаты
- Zeit Next.js, о котором я отдельно написал статью Создаем изоморфное/универсальное приложение на Next.JS + Redux
- Walmart Labs Electrode
- Facebook Create React App (+ some parts of custom solution)
- Кастомщина, например в моей статье Упрощаем универсальное/изоморфное приложение на React + Router + Redux + Express
- Webpack для сборки + Webpack Blocks для облегчения конфигурации
- ExpressJS сервер
- Redux для управления состоянием
- React Router для структурирования кода и навигации
- Create React Server для связывания всего воедино при серверном рендеринге
Что нужно помнить разрабатывая современное приложение
Кешируемые части
Если собрать все приложение в один гигантский JS файл, то он будет грузиться вечность, пока юзеры будут наблюдать анимацию загрузки. Исследования показывают, что юзеры предпочитают покидать сайты, которые не успевают загрузиться за 3 секунды.
- Next.js автоматически создает отдельный chunk для каждой страницы
- Electrode, Create React App и кастом используют React Router. Webpack, в свою очередь, обладает так называемым code-splitting, который прекрасно работает с React Router. Кстати, динамические
import()теперь поддерживаются Webpack 2. Для тех, кто до сих пор сидит на Webpack 1.x тоже есть решение. У Electrode в качестве решения есть специальный archetype.
Управление статикой
С одной стороны приложение должно доставить как можно больше сразу, чтоб не ходить лишний раз по сети за ресурсами (картинки, стили, скрипты), например, т.н. vendor-chunk со всеми библиотеками, важные CSS скрипты. Но в то же время мы хотим доставлять как можно меньше, только то, что необходимо на данной странице.
- Create React App не дает модифицировать конфиг Webpack'а, так что здесь пролет
- Next.js, Electrode и custom позволяют модифицировать конфиг Webpack'а и таким образом через плагины менять что куда попадает
Server Side Rendering
Приложение может загрузить все необходимые данные и сгенерировать готовый HTML, который вместе с данными будет отдан клиенту, таким образом практически устраняется задержка между первоначальной загрузкой и моментом, когда хоть что-то появляется на сайте, хотя бы для просмотра. Про возможность всем этим пользоваться не говорим, это отдельная тема.
Google ранжирует сайты с помощью времени отклика в том числе. Не смотря на то, что поисковики умеют рендерить AJAX сайты, это плохо влияет на позицию, поэтому сайт обязан предоставлять некую версию, которую смогут переварить поисковики. С этим есть проблема, если мы захотим загрузить все данные, а не только основную контентную область, как дать понять серверному рендеру, что все загрузки закончены? Для этого пока нет сложившегося подхода. В кастомном варианте мы можем распарсить дерево компонентов и выдернуть из каждого, например, статический метод getInitialProps (по аналогии с Next.js), и когда все эти методы вернут свои значения, тогда и можно рендерить.
- Create React App серверному рендерингу не обучен, и вряд ли когда-нибудь научится
- У Next.js и Electrode это главная фишка
- Если мы все строим с нуля (т.е. кастом) то и это придется построить тоже
Для того, чтобы компоненты сами могли сказать, что им нужно из данных, был придуман Relay + GraphQL, но минусы перевешивают его плюсы. Для него надо отдельный сервер. Он дико словоблуден (сама схема + мутации чудовищно громоздки). Сложность кода, который нужно написать для простейших действий практически убивает все бонусы. Объяснить это кому-то из команды с нуля тоже проблематично. К тому же потребуется специальный Babel трансформ, а также утилита для скачивания и компилиции схемы. А в результате все равно получится несовместимое с SEO нечто. Стоит признать, что если вы только потребляете существующий GraphQL API, то ситуация чуть получше. Это довольно многообещающая технология, хочется верить, что у ребят получится сделать ее более дружелюбной. Стоит еще отметить такую штуку как Apollo Framework.
Во время исследования я создал пару библиотек, которые могут помочь с Server Side Rendering:
- Create React Server (для кастома)
- Next.js and Redux
Производительность Server Side Rendering
В какой-то момент производительности сервера перестанет хватать для рендеринга компонентов. В таком случае некоторые компоненты можно начать кешировать.
- У Electrode есть плагин, а для боевом режиме весь код берется из предварительно скомпилированных файлов
- Next.js ничего не предоставляет для этого пока что, но код весь компилирует и сохраняет
- В кастомном решении, как и обычно, все придется делать самому, есть способы процесс упростить, например чере Create React Server, но сохранение компилированного кода для сервера им не контролируется
- Create React App серверного рендеринга не имеет
CSS preprocessors
Многие пользуются LESS/SASS/Stylus/PostCSS для того, чтобы использовать переменные и микс-ины в своих стилях, так код получается чище и лаконичнее.
- Electrode и Next.js ничего для этого не предоставляют, вы можете кастомизировать конфиг Webpack, но это крайне не рекомендуется
- Create React App не поддерживает вовсе, и у них даже есть объяснение (TL;DR используйте композицию)
- В кастомном проекте можно использовать обычные Webpack Loader'ы
В любом из кандидатов всегда можно создать отдельный процесс сборки/watch'а за стилями SASS/LESS, как например в Create React App, но полученный CSS нужно руками как-то подключать к приложению (в Create React App его можно импортировать напрямую). В этом случае Critical CSS и Hot Module Reloading, а также Server-Side Rendering стилей становится вашей заботой, что нивелирует преимущества не-кастомных решений.
Интеграция со сторонними UI библиотеками
Очевидно, что используя лучшие библиотеки для интерфейсов мы улучшаем вид продукта, ускоряем его создание, особенно если мы говорим о таких вещах как Twitter Bootstrap или Material Design. Эти библиотеки обычно идут в комплекте с CSS, который нужно каким-то образом включить в приложение.
- В Next.js вы можете подключить стили напрямую в
_document.js(главный шаблон), но тогда файл со стилями должен лежать в директорииstatic; если же вы используете препроцессоры, то лучше включить CSS из-под ваших других файлов со стилями (ну а уже их подключить через кастомный_documentи сборщик или кастомный конфиг Webpack) - Electrode позволяет импортировать CSS напрямую; так же можно через кастомный шаблон HTML (но тогда это снова надо включать в билд); или снова кастомный Webpack loader
- Create React App разрешает импортировать CSS
Critical CSS / Above The Fold CSS
Грубо говоря, определенные стили можно загрузить вместе с HTML, таким образом пользователи никогда не увидят т.н. вспышку нестилизованного контента (FOUC). Полезно, но совершенно не критично, т.к. современные сети достаточно производительны и стилевая таблица весом до 100КБ это вообще ни о чем.
- У Electrode есть специальный плагин
- Next.js использует CSS in JSX поэтому CSS всегда включен непосредственно в компонент
- Для остальных можно слепить что-то свое
Поддержка скинов
Это редкий кейс нужный далеко не всем, но ��ногие приложения поддерживают разные скины для разных брендов или просто для удобства пользователей (ну например темная или светлая цветовая схема).
- Next.js использует CSS в JS, поэтому можно использовать JS-подход, сделать функцию для CSS и набить стили переменными, а сами переменные получать в виде аргументов
- Electrode использует CSS модули, которые переменных не имеют
- Если вы используете CSS препроцессоры с некой билд-процедурой, то базовые стили можно набить переменными, создать несколько входных точек с разными оверрайдами переменных и включать одну из точек в приложение (Twitter Bootstrap так работает, например)
Экосистема и сообщество
Жить внутри экосистемы, хорошо спроектированной и документированной, всегда приятнее, чем разбираться в куче мелких библиотек: меньше мороки с зависимостями, возможность следовать направлению, заданному авторами, кучи примеров. Вспоминается фраза, что если вы не используете фреймворк, то в итоге вы его напишете сами.
- У Electrode ~500 звезд на Github, активное сообщество отвечает на тикеты и вопросы на StackOverflow. Документация сумбурная и не очень понятная, с жутко раздутыми примерами, но если разобраться — жить можно.
- У Next.js ~9000 звезд на Github, супер быстрый отклик на пулл-реквесты и тикеты. Есть документация для простых и сложных случаев, огромное количество примеров на все случаи жизни.
- У Create React App ~21.500 звезд на Github, э��о самое большое сообщество. Сам фреймворк настолько мелкий и примитивный, что там документировать даже особо нечего, однако авторы умудрились написать кучу примеров и рецептов.
- В кастомном приложении вы сами по себе, все примеры, документация, сообщества раскиданы по всему интернету, все нужно искать, самому выбирать толковые решения, и вы получаетесь единственным носителем знания, что очень плохо для компании. Документацию Вам придется так же писать самому.
Тесты
Думаю уже никто не будет спорить, что в современном мире без тестов вообще никуда.
- Electrode использует PhantomJS для честных браузерных тестов и отдельные серверные тесты, это лучшее покрытие из возможных
- Create React App поставляется вместе с Jest, он хорош, но не запускается в браузере, поэтом как бы не совсем честное покрытие
- Для Next.js и кастома все делаем сами
Boilerplate, конфигурация, начальная стоимость
Поскольку мы планируем делать много проектов основанных на одном стандарте, мы хотим иметь как можно меньше мороки с первоначальной настройкой и запуском наших решений. Для этого желательно чтоб инициализация плодила как можно меньше файлов и конфигов.
- Next.js и Create React App идут вообще без конфигурации, просто разложите нужные файлы в соответствии с соглашением и все магически заработает
- Electrode идет в комплекте с генератором для Yeoman который выдает просто чудовищно огромную гору файлов, придется потратить время на то, чтоб привыкнуть, что где лежит. В этой горе буквально все: конфиги, код сервера, код для серверных страниц, клиентский код, и пока я не нашел способа хоть как то это оптимизировать.
- Кастом всегда будет иметь некоторое количество кода, от которого не избавиться. Но если вы делаете десятки проектов, то в какой-то момент у вас должны сформироваться некие повторяющиеся паттерны, которые можно переместить в абстракцию. И может даже опен-сорснуть ;). Честно говоря, это же можно сделать и с Electrode, и мне кажется, они и сами к этому придут.
Кривая обучения в начале и в сложных случаях, сложность кастомизации, поддержка в будущем
Предположим, вы будете работать с приложением в составе команды, в таком случае вам придется каким-то образом обучать своих коллег. Документация и зафиксированные паттерны — это хорошая основа для легкого обмена знаниями внутри команды и между командами.
Возможность кастомизировать что угодно тоже должна присутствовать, т.к. мы живем в реальной жизни и в ней что угодно может поменяться когда угодно, требования же не вечны. И очень не хотелось бы упереться в какое-то жесткое ограничение фреймворка.
- Create React App самый простой для понимания в начале, но он вообще не дает никаких средств для модификации. Его исходный код довольно суров, поэтому если вы планируете сделать “eject” (то есть вынуть все скрипты для сборки, все конфиги и прочие кишки наружу), то лучше сразу откажитесь от этого направления в пользу кастома. В комплект поставки входит linter. Поскольку API вообще отсутствует, а количество соглашений минимально — апгрейды пройдут безболезненно.
- Next.js заботится о некоторых аспектах, понимение того, как именно он это делает, потребует немного времени. Большинство простых кейсов имеют примеры, однако когда требуется что-то сложное или нестандартное — проще застрелиться, код превращается в кашу и сдабривается большим количеством шатких сторонних решений. По большому счету любой шаг в сторону грозит болью. Linter в комплект не входит. Схожая картина с апгрейдом, API минимален, соглашения легко соблюдать.
- Поскольку Electrode сильно базируется на конфигурации и большинство скриптов открыты с самого начала, это вызывает легкий шок в начале, но как только вы со всем разберетесь — жить быстро станет проще. Таким образом для более сложных случаев вы будете лучше подготовлены. К сожалению, под капотом все равно происходит порядочное количество колдунства, поэтому совсем легко не будет никогда. В комплект входит отлично настроенный ESLint. Здесь с апгрейдом все похуже. Поскольку количество кишок, торчащих наружу, довольно велико, то шанс что-то сломать в будущем тоже велик.
- В кастоме вы сами по себе. Как сделаете, так и будет. Сами ищете лучшие практики, сами подбираете решения, и так с начала и до последнего дня проекта. С агрейдом все вообще совсем плохо, что угодно может сломаться когда угодно.
Интернационализация
Для удобства строки с локализацией должны быть в стандартном формате, с подстановками и склонениями. Желательно в таком, который может быть легко воспринят фирмами, занимающимися переводами, потому что именно они будут больше всего с ними возиться.
Ни один из представленных фреймворков не дает никаких средств для перевода. Однако, их несложно добавить. После поисков я выяснил, что централизованный асинхронный загрузчик работает лучше всего, в таком случае каждый язык становится отдельным chunk'ом. Добиться этого можно создав функцию, которая принимает язык как параметр и отдает асинхронно загруженные строки для языка. Что-то типа const loadStrings(lang) => import('./strings/'+lang) (данная конструкция вряд ли сразу заработает, но суть должна быть понятна, перед ней можно еще насоздавать Webpack Context'ов, чтоб гарантировать связку 1 язык = 1 чанк.
Библиотеки: FormatJS и Format Message (и та и та работает с так называемым ICU Format).
Организовывать строки лучше всего по токенам, т.е. {TOKEN: {en: 'English', fr: 'French'}}, так проще для разработчиков и переводчиков. Обратный подход, где "как бы токеном" является английская строка себя не оправдал.
Сравнение
| Дисциплина \ Название | React App | Electrode | Next.js | Custom |
|---|---|---|---|---|
| Dynamic Routes | да | да | DIY | да |
| Server rendering | нет | да | да | DIY |
| SSR optimization | нет SSR | да | нет | DIY |
| CSS | да | да | ночной кошмар | DIY легко |
| Preprocessors | ночной кошмар | ночной кошмар | ночной кошмар | DIY легко |
| Critical CSS | DIY | плагин | нет | DIY |
| Сообщество | большое | есть | есть | ты сам по себе |
| Тесты | jest | phantom | DIY | DIY |
| Код основы | ноль | много | ноль | очень много |
| Конфигурация | ноль | много | ноль | очень много |
| Документация | хорошая | так себе | в наличии | разрозненная |
| Обучение простому | сел и поехал | приемлемо | легко | ночной кошмар |
| Дальнейшее обучение | проще умереть | тяжело | тяжело | ночной кошмар |
| Движок сервера | nginx | node | node | node |
| Кастомизация | eject и смерть | приемлемо | ночной кошмар | все что угодно |
| Первоначальная установка | легко | словоблудно | легко | ночной кошмар |
| Предсказуемость | хорошая | так себе | плохая | ночной кошмар |
| Апгрейды | шикарно | неплохо | так себе | ночной кошмар |
Вердикт
Если вы не собираетесь заниматься серверным рендерингом (вы ничего не продаете, приложение не индексируется гуглом, и вообще приватное), то можете смело смотреть в сторону Create React App, он самый популярный и простой. Его даже можно немножечко кастомизировать. Только eject не делайте, оно того не стоит.
Если серверный рендеринг нужен, и вы готовы смириться с кое-какими ограничениями, то выбирайте Electrode (в качестве условия вас так же не должны пугать большое количество файлов, словоблудность и конфигурации). Это так же хороший выбор, если вас беспокоит производительность.
Если вы готовы мириться с еще большими ограничениями и любите минимализм, то присмотритесь к Next.js.
Ну и наконец, всегда есть кастом. К счастью, библиотеки типа Webpack Blocks, Create React Server, React Router, Redux и прочие делают жизнь сильно проще. Единственная проблема это обмен знаниями и выработка процессов для быстрого создания приложений без повторения одного и того же кода каждый раз.