«Если вам не стыдно за первую версию своего продукта, то вы опоздали с выходом на рынок»
Эти слова оправдывают развитие фронтенда Lingualeo с самого начала, с 2010 года. В сложном многоплатформенном продукте с 5-летней историей код наслоился чуть толще, чем пласты почвы над археологической Трипольской культурой. И вот, этот день настал: мы решили разбежаться и прыгнуть как следует, обновив платформу web-клиента. Ниже мы расскажем, что сделали, чтобы приземлиться как можно дальше от точки отсчета.
Пиарщице, безрассудно пытавшейся зарыться в понимание всего этого, пришлось сказать, что главные технологии, на которых основан фронтенд Lingualeo, — костыли и велосипеды. Надеемся, ей хватит ума не писать это на Хабр.
Шаг первый. Откопать пиарщицу
Фронтенд Lingualeo работает на куче разнообразных библиотек, с кодом, распределенным от тех мест, где грусть-пичаль, до тех мест, где “bleeding edge” — тоже своего рода грусть-пичаль :-D. В стародавние времена на сайте Lingualeo все начиналось с JS-файлов с вкраплениями jQuery. Потом возникла необходимость это разбивать, и появилось некоторое подобие «виджетов», к которым прикрутили глобальную шину событий. Сама система «виджетов» претерпела несколько итераций, пока мы не решили перебраться на так называемый компонентный подход. После некоторых колебаний и поисков мы добавили немного энтропии к суете вокруг React-а.
Шаг второй. Слепить, из того, что было, и немного добавить от себя
Итак, для тех, кто далек от темы и по ссылке выше поленился перейти, сообщаем: React — это JS библиотека для построения пользовательских интерфейсов. К моменту начала наших экспериментов React вышел в версии 0.13. С ним и приступили к работе. Непосредственно с React «подружиться» не удалось по нескольким причинам:
- нам надо было как-то поддерживать и скрещивать предыдущий код и код на React,
- на старте не хотелось отказываться от привычных текстовых шаблонов,
- было интересно копнуть глубже и написать что-нибудь своё,
- наш сборщик (которому тоже много лет) не научился работать с jsx.
Так что решили «обёртку» написать свою, а систему для создания и работы с Virtual DOM использовать готовую:
- https://github.com/Matt-Esch/virtual-dom для построения Virtual DOM и сравнивания его с предыдущим Virtual DOM,
- https://github.com/marcelklehr/vdom-virtualize — для преобразования html в Virtual DOM,
- https://github.com/nthtran/vdom-to-html — для преобразования Virtual DOM в html (думали пригодится, но нет, используем его, в основном, для отладки).
Кстати, мы уже упоминали это в посте о новой тренировке грамматики.
«Обёртку» старались писать с совместимостью по API с React-компонентами, т.е. проходящими тот же самый жизненный цикл со следующими хуками (hooks):
(componentWillMount, componentDidMount, componentWillUnmount,
componentWillUpdate, componentDidUpdate,
componentWillReceiveProps)
Так можно какие-то общие случаи искать, будто мы работаем с React, да и есть задел на более простой переход на React без переписывания всех компонентов.
Шаг третий. Спуститься на землю и вновь полюбить жизнь
Вся эйфория от перехода на задуманный план с Virtual DOM немного притупилась, когда созрел вопрос о том, как же быть с потоком данных и обработкой пользовательских событий. Архитектурный подход Flux показался нам каким-то уж очень абстрактным решением. Написав с его помощью первую версию новой тренировки грамматики, мы попятились и повернули в сторону Redux. Этот подход мы считаем более прямым и лаконичным: единое хранилище данных, наличие middlewares и любимые нами чистенькие функции-handler’ы в качестве обработчиков событий. Но мы были бы не мы, не написав своей прослойки для реализации этого подхода, на что было несколько разных причин. Последние, в основном, упирались в наш сборщик: он не поддерживает ES6, на котором написан Redux.
Redux в представлении разработчиков
Шаг четвертый. Мечтать и воплощать
В итоге мы потихоньку тянемся к светлому будущему, в котором:
- в компонентах минимум бизнес-логики, они просто отражают состояние и слушают события от пользователя;
- вся бизнес-логика (реакция на события) накапливается внутри стора, а точнее, Reducer'a, возвращающего новое состояние стора на основании старого состояния и действия;
- какие-то асинхронные действия выносятся в action creators или даже в middlewares;
- анимация — наш камень преткновения, ужас всех фронтендеров — запихивается в компоненты (в правильности такого подхода уверенность есть не у всех).
На самом деле до сих пор идёт притирка к данному подходу, и не на все вопросы ответы очевидны, как в случае с анимацией, например, но в целом данный подход нам весьма нравится и неплохо облегчает жизнь в тех местах, где он внедрен.
В планах же на будущее — переход на Virtual DOM по всему сайту и внедрение
Кстати, будем весьма признательны хабровчанам за комментарии о том, как они работают с анимацией в React: интересуют сложные случаи, когда происходит несколько взаимодействующих анимаций, либо взаимодействие с компонентом во время анимации). Да и вопрос о том, как осуществлялся переход от одной архитектуры к другой у вас нам тоже интересен ;-)
Искренне ваша фронтенд-команда Lingualeo.
Следите за нашими новостями в Twitter, Facebook, vkontakte и Instagram.
Off topic: Lingualeo ждет в свои ряды разработчиков iOS и Android. О всех открытых вакансиях читайте в блоге на сайте lingualeo.com. Ждем ваши резюме!
Комментарии (29)
maxru
10.11.2015 14:44ждет в свои ряды разработчиков
Хантим-увольняем. Отличная стратегия же.LinguaLeo
10.11.2015 15:34+1Мы так не думаем. И шутить по этому поводу тоже не будем. Компания вышла на самоокупаемость, и сейчас у нас появились возможность и средства дать людям интересную работу. Спасибо за понимание.
uve
10.11.2015 17:00Добавьте пожалуйста в интерфейс под iOS класс .noselect (это ведь webapp?) чтобы постоянно не выделялся текст при использовании.
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}Rirus
11.11.2015 15:58Можете привести пример, в каком разделе приложения у вас происходит выделение текста? В грамматических курсах? Или джунглях?
prishelec
10.11.2015 18:07До сих пор не синхронизируются данные для тренировок под android.Я уже эту тему поднимал месяца 3-4 назад. Так и не исправлено.
На сайте, к примеру, доступно для тренировок 1500, а на телефоне 800.
prishelec
10.11.2015 18:10+1Плюс: будет ли возможно отмены изучения материала выбранного на тренировках. Бывает, случайно не тот выберешь, и чтобы он не висел в списках приходиться подтверждать «я понял текст».
AndreyNagih
10.11.2015 18:56+1Спасибо за статью. Могли бы вы раскрыть подробнее, чем вам не подошла архитектура Flux, и какие плюсы вы получили от Redux?
unel
10.11.2015 22:48+1Flow какой-то слишком аморфный: такое впечатление, что многие детали реализации ребята из фейсбука решили отдать на откуп сторонним разработчикам, а про некоторые и вообще забыли (например, асинхронные операции).
Так же не очень было непонятно, зачем необходимо несколько сторов, которое само по себе вводит дополнительные сущности вроде dispatcher'а и функции waitFor.
На самом деле мы его попробовали и получилось как-то… Не очень. Чисто по ощущениям.
Redux отличился более продуманной архитектурой и более внятной документацией.
Очень подкупала его идея декларативности action-ов, хотя в примере с async actions автор почему-то от неё отходит. Самое забавное то, что понимаешь, как можно сделать то же самое подекларативней, за счёт middlewares.
Ну и data-flow как-то в redux более ясный: Есть стор (один, единственный), единственная цель которого — хранить текущее состояние и уведомлять о его изменениях и reducer, единственная цель которого — возвращать новое состояние исходя из переданных ему состояния и action-а.
Возможно, flow тоже подразумевал под собой эти идеи, но в redux они более выражены и лажат на поверхности =)
arusakov
10.11.2015 19:07Достаточно сумбурно, но в целом познавательно. Совсем недавно на highload 2015 был отличный доклад на аналогичную тему перевода старого фронтенда на новый лад в Акронис. Так вот ребята не испугались и таки провели постепенный переход с ExtJS на React + собственную реализацию Flux. Соответственно встает несколько вопросов:
1) Что же у вас за такой магический сборщик, в который и JSX не вставляется, и ES6 не подтягивается, но выкинуть на свалку его вы не захотели? И вместо того, чтобы взять готовые webpack + babel, вы решили написать свою обвязку к virtual dom? На мой взгляд, если не начать переводить код на ES6 сейчас, то через год-два фронтендеры будут задавать вам на собеседованиях дополнительные вопросы типо: «IE6 вы тоже еще поддерживаете да?».
2) Самому очень нравится Redux + чистые функции. Но используете ли вы какую-то библиотеку для immutable данных или свое решение?unel
10.11.2015 21:38+1Что же у вас за такой магический сборщик, в который и JSX не вставляется, и ES6 не подтягивается, но выкинуть на свалку его вы не захотели?
Почему же не захотели? Очень даже хотим заменить его на webpack (даже изучаем сейчас это дело), но это несколько сложней, да и нужно отвлекать на это дело бекендеров (чтобы встроить в текущую систему деплоя). А вот поиграться с реактом и прочими штуками руки чешутся, да и бекенд не требуется)) Но всё же мы планируем и сборщик нормальный и es6 (я так вообще мечтаю о TypeScript :-D), ну и пересесть с «велосипеда» на react. Но это всё упирается во время, да и про другие задачи нельзя забывать)igor-petrov
10.11.2015 22:53Подозреваю, что бекендеров вам особо отвлекать и не придется — настройка webpack лежит на ваших плечах, с их стороны нужно будет только прописать команду в скрипте сборки, если у вас туда нет доступа. Ну и поставить webpack на машине-сборщике.
unel
11.11.2015 13:33Ну, почти) ведь надо ещё
— продумать конфигурацию для puppet, чтобы он установил nodejs, подтянул нужные либы
— понять, каким образом статика выливалась на cdn и либо как-то перенести такой же механизм в webpack, либо как-то переделать
ну и куча всяких незаметных по началу нюансов, которые всплывают в процессе)
unel
10.11.2015 21:39Но используете ли вы какую-то библиотеку для immutable данных
Immutable данные пока не используем. Вообще, хотелось использовать Immutablejs, но либа показалась нам большеватой. А пока reducer всегда работает с deep-copy стейта, изменяя его по старинке. Уведомляем об изменениях стейта всегда, а не только когда реально произошли изменения… Пока вроде не сильно проседает в производительности, ну а как просядет — впору будет задуматься и об immutable структурах.
igor-petrov
10.11.2015 22:50После прочтения показалось, что первым делом вам нужно избавиться от этого загадочного сборщика, который вставляет такие палки в колеса. Нашли время, готовы были перейти на bleeding edge — но в итоге из-за него написали свой велосипед (хоть и продуманный, хоть и react-подобный).
unel
11.11.2015 13:38Всё верно) Но сборщик оказалось заменять несколько сложней, чем внедрить что-то своё)
Заодно в качестве бонуса немного лучше поняли, как это всё работает внутри)
Так-то мы не исключаем переход на обычные react/redux, но на это будем смотреть уже после внедрения и обкатки вебпака)
nightstalker
11.11.2015 01:40что у вас там за такой загадочный сборщик, которому сложно дописать поддержку того же бабеля?
EngineerSpock
11.11.2015 12:27Вы WinPhone app совсем забросили, или как? Кстати, с какой-то версии стало дико неудобно в несколько кликов добавлять новое слово. Раньше можно было в один клик, а ещё оно тормозит сильно на Lumia 920, а это не самый слабый WinPhone.
Rirus
11.11.2015 15:57+1Добавьте тайл для добавления слова на рабочий стол. Это самый быстрый и удобный способ (среди всех 3х платформ) для добавления слова или просмотра перевода, сам им пользуюсь. По факту, всё приложение не запускается, открывается только нужная часть. Тайл можно добавить с экрана добавления, ну или Лев его предложить должен.
А по поводу «не самый слабый WinPhone» — это вы MS расскажите; 1020 тормозит так, что некоторые приложения просто не хотят запускаться. Проблема в первую очередь аппаратная: приложения написанные под Windows Phone 8.1 работают на новом движке, который не оптимизирован под девайсы, изначально выпущенные с 8.0. Приложение Lingualeo реализованно как Universall App (тот, который для 8.1, где общее всё, кроме дизайна).EngineerSpock
11.11.2015 16:21Ух ты, за тайл огромное спасибо! Как-то не очевидно для меня это оказалось.
По поводу WinPhone, ну, не знаю, подавляющая часть приложений летает, вроде бы.Rirus
11.11.2015 17:04Про тайл в велкоме Лев говорит, возможно пропустили, или уже просто не нажимаете на Льва :)
А про винфон — тот же 6tag работает с перебоями. Т.е. если сравние работу приложений на *20 моделях и хотя бы на *30 (а ведь уже *50 выходят, но уже с Windows 10), то почуствуете разницу. Это примерно как сравнивать Lumia 800 и 820.
IDMan
Совсем не в тему, но у вас очень красивое и удобное приложение под iOS, только за одни титры под видео, с перекладом по словах, можно отдать ему сердце :)
LinguaLeo
Спасибо за теплые слова)