Каждый раз начиная писать 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 для структурирования кода и навигации
- React Router Redux Middleware для связывания всего воедино при серверном рендеринге
Что нужно помнить разрабатывая современное приложение
Кешируемые части
Если собрать все приложение в один гигантский 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:
- React Router Redux Middleware (для кастома)
- Next.js and Redux
Производительность Server Side Rendering
В какой-то момент производительности сервера перестанет хватать для рендеринга компонентов. В таком случае некоторые компоненты можно начать кешировать.
- У Electrode есть плагин, а для боевом режиме весь код берется из предварительно скомпилированных файлов
- Next.js ничего не предоставляет для этого пока что, но код весь компилирует и сохраняет
- В кастомном решении, как и обычно, все придется делать самому, есть способы процесс упростить, например чере React Router Redux Middleware, но сохранение компилированного кода для сервера им не контролируется
- 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, React Router Redux Middleware, React Router, Redux и прочие делают жизнь сильно проще. Единственная проблема это обмен знаниями и выработка процессов для быстрого создания приложений без повторения одного и того же кода каждый раз.
Автор: dfuse