image

«Если вам не стыдно за первую версию своего продукта, то вы опоздали с выходом на рынок»

Эти слова оправдывают развитие фронтенда Lingualeo с самого начала, с 2010 года. В сложном многоплатформенном продукте с 5-летней историей код наслоился чуть толще, чем пласты почвы над археологической Трипольской культурой. И вот, этот день настал: мы решили разбежаться и прыгнуть как следует, обновив платформу web-клиента. Ниже мы расскажем, что сделали, чтобы приземлиться как можно дальше от точки отсчета.

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

Шаг первый. Откопать пиарщицу


Фронтенд Lingualeo работает на куче разнообразных библиотек, с кодом, распределенным от тех мест, где грусть-пичаль, до тех мест, где “bleeding edge” — тоже своего рода грусть-пичаль :-D. В стародавние времена на сайте Lingualeo все начиналось с JS-файлов с вкраплениями jQuery. Потом возникла необходимость это разбивать, и появилось некоторое подобие «виджетов», к которым прикрутили глобальную шину событий. Сама система «виджетов» претерпела несколько итераций, пока мы не решили перебраться на так называемый компонентный подход. После некоторых колебаний и поисков мы добавили немного энтропии к суете вокруг React-а.

image

Шаг второй. Слепить, из того, что было, и немного добавить от себя


Итак, для тех, кто далек от темы и по ссылке выше поленился перейти, сообщаем: React — это JS библиотека для построения пользовательских интерфейсов. К моменту начала наших экспериментов React вышел в версии 0.13. С ним и приступили к работе. Непосредственно с React «подружиться» не удалось по нескольким причинам:

  • нам надо было как-то поддерживать и скрещивать предыдущий код и код на React,
  • на старте не хотелось отказываться от привычных текстовых шаблонов,
  • было интересно копнуть глубже и написать что-нибудь своё,
  • наш сборщик (которому тоже много лет) не научился работать с jsx.

Так что решили «обёртку» написать свою, а систему для создания и работы с Virtual DOM использовать готовую:


Кстати, мы уже упоминали это в посте о новой тренировке грамматики.

«Обёртку» старались писать с совместимостью по API с React-компонентами, т.е. проходящими тот же самый жизненный цикл со следующими хуками (hooks):

(componentWillMount, componentDidMount, componentWillUnmount,
componentWillUpdate, componentDidUpdate,
componentWillReceiveProps)

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

Шаг третий. Спуститься на землю и вновь полюбить жизнь


Вся эйфория от перехода на задуманный план с Virtual DOM немного притупилась, когда созрел вопрос о том, как же быть с потоком данных и обработкой пользовательских событий. Архитектурный подход Flux показался нам каким-то уж очень абстрактным решением. Написав с его помощью первую версию новой тренировки грамматики, мы попятились и повернули в сторону Redux. Этот подход мы считаем более прямым и лаконичным: единое хранилище данных, наличие middlewares и любимые нами чистенькие функции-handler’ы в качестве обработчиков событий. Но мы были бы не мы, не написав своей прослойки для реализации этого подхода, на что было несколько разных причин. Последние, в основном, упирались в наш сборщик: он не поддерживает ES6, на котором написан Redux.

image

Redux в представлении разработчиков

Шаг четвертый. Мечтать и воплощать


В итоге мы потихоньку тянемся к светлому будущему, в котором:

  • в компонентах минимум бизнес-логики, они просто отражают состояние и слушают события от пользователя;
  • вся бизнес-логика (реакция на события) накапливается внутри стора, а точнее, Reducer'a, возвращающего новое состояние стора на основании старого состояния и действия;
  • какие-то асинхронные действия выносятся в action creators или даже в middlewares;
  • анимация — наш камень преткновения, ужас всех фронтендеров — запихивается в компоненты (в правильности такого подхода уверенность есть не у всех).

На самом деле до сих пор идёт притирка к данному подходу, и не на все вопросы ответы очевидны, как в случае с анимацией, например, но в целом данный подход нам весьма нравится и неплохо облегчает жизнь в тех местах, где он внедрен.
В планах же на будущее — переход на Virtual DOM по всему сайту и внедрение нормального популярного ныне сборщика webpack. Разумеется, всё это требует времени и осторожности.

Кстати, будем весьма признательны хабровчанам за комментарии о том, как они работают с анимацией в React: интересуют сложные случаи, когда происходит несколько взаимодействующих анимаций, либо взаимодействие с компонентом во время анимации). Да и вопрос о том, как осуществлялся переход от одной архитектуры к другой у вас нам тоже интересен ;-)

Искренне ваша фронтенд-команда Lingualeo.

Следите за нашими новостями в Twitter, Facebook, vkontakte и Instagram.

Off topic: Lingualeo ждет в свои ряды разработчиков iOS и Android. О всех открытых вакансиях читайте в блоге на сайте lingualeo.com. Ждем ваши резюме!

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


  1. IDMan
    10.11.2015 13:59

    Совсем не в тему, но у вас очень красивое и удобное приложение под iOS, только за одни титры под видео, с перекладом по словах, можно отдать ему сердце :)


    1. LinguaLeo
      10.11.2015 15:21

      Спасибо за теплые слова)


  1. maxru
    10.11.2015 14:44

    ждет в свои ряды разработчиков

    Хантим-увольняем. Отличная стратегия же.


    1. LinguaLeo
      10.11.2015 15:34
      +1

      Мы так не думаем. И шутить по этому поводу тоже не будем. Компания вышла на самоокупаемость, и сейчас у нас появились возможность и средства дать людям интересную работу. Спасибо за понимание.


      1. maxru
        12.11.2015 13:13
        +3

        Ну, тестировщика же у меня угнали, а потом почти сразу сократили. Так-то.


  1. uve
    10.11.2015 17:00

    Добавьте пожалуйста в интерфейс под iOS класс .noselect (это ведь webapp?) чтобы постоянно не выделялся текст при использовании.
    .noselect {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    }


    1. Rirus
      11.11.2015 15:58

      Можете привести пример, в каком разделе приложения у вас происходит выделение текста? В грамматических курсах? Или джунглях?


  1. prishelec
    10.11.2015 18:07

    До сих пор не синхронизируются данные для тренировок под android.Я уже эту тему поднимал месяца 3-4 назад. Так и не исправлено.
    На сайте, к примеру, доступно для тренировок 1500, а на телефоне 800.


  1. prishelec
    10.11.2015 18:10
    +1

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


  1. AndreyNagih
    10.11.2015 18:56
    +1

    Спасибо за статью. Могли бы вы раскрыть подробнее, чем вам не подошла архитектура Flux, и какие плюсы вы получили от Redux?


    1. unel
      10.11.2015 22:48
      +1

      Flow какой-то слишком аморфный: такое впечатление, что многие детали реализации ребята из фейсбука решили отдать на откуп сторонним разработчикам, а про некоторые и вообще забыли (например, асинхронные операции).
      Так же не очень было непонятно, зачем необходимо несколько сторов, которое само по себе вводит дополнительные сущности вроде dispatcher'а и функции waitFor.

      На самом деле мы его попробовали и получилось как-то… Не очень. Чисто по ощущениям.

      Redux отличился более продуманной архитектурой и более внятной документацией.
      Очень подкупала его идея декларативности action-ов, хотя в примере с async actions автор почему-то от неё отходит. Самое забавное то, что понимаешь, как можно сделать то же самое подекларативней, за счёт middlewares.

      Ну и data-flow как-то в redux более ясный: Есть стор (один, единственный), единственная цель которого — хранить текущее состояние и уведомлять о его изменениях и reducer, единственная цель которого — возвращать новое состояние исходя из переданных ему состояния и action-а.

      Возможно, flow тоже подразумевал под собой эти идеи, но в redux они более выражены и лажат на поверхности =)


      1. PQR
        11.11.2015 11:24

        Вы вместо слова Flux случайно написали Flow? Или я чего-то не понял?


        1. unel
          11.11.2015 11:31

          Ох) Да, конечно же я имел в виду Flux)


  1. arusakov
    10.11.2015 19:07

    Достаточно сумбурно, но в целом познавательно. Совсем недавно на highload 2015 был отличный доклад на аналогичную тему перевода старого фронтенда на новый лад в Акронис. Так вот ребята не испугались и таки провели постепенный переход с ExtJS на React + собственную реализацию Flux. Соответственно встает несколько вопросов:
    1) Что же у вас за такой магический сборщик, в который и JSX не вставляется, и ES6 не подтягивается, но выкинуть на свалку его вы не захотели? И вместо того, чтобы взять готовые webpack + babel, вы решили написать свою обвязку к virtual dom? На мой взгляд, если не начать переводить код на ES6 сейчас, то через год-два фронтендеры будут задавать вам на собеседованиях дополнительные вопросы типо: «IE6 вы тоже еще поддерживаете да?».
    2) Самому очень нравится Redux + чистые функции. Но используете ли вы какую-то библиотеку для immutable данных или свое решение?


    1. unel
      10.11.2015 21:38

      Достаточно сумбурно

      Ровно настолько, насколько остуществлялся и сам переход :-)


    1. unel
      10.11.2015 21:38
      +1

      Что же у вас за такой магический сборщик, в который и JSX не вставляется, и ES6 не подтягивается, но выкинуть на свалку его вы не захотели?

      Почему же не захотели? Очень даже хотим заменить его на webpack (даже изучаем сейчас это дело), но это несколько сложней, да и нужно отвлекать на это дело бекендеров (чтобы встроить в текущую систему деплоя). А вот поиграться с реактом и прочими штуками руки чешутся, да и бекенд не требуется)) Но всё же мы планируем и сборщик нормальный и es6 (я так вообще мечтаю о TypeScript :-D), ну и пересесть с «велосипеда» на react. Но это всё упирается во время, да и про другие задачи нельзя забывать)


      1. igor-petrov
        10.11.2015 22:53

        Подозреваю, что бекендеров вам особо отвлекать и не придется — настройка webpack лежит на ваших плечах, с их стороны нужно будет только прописать команду в скрипте сборки, если у вас туда нет доступа. Ну и поставить webpack на машине-сборщике.


        1. unel
          11.11.2015 13:33

          Ну, почти) ведь надо ещё
          — продумать конфигурацию для puppet, чтобы он установил nodejs, подтянул нужные либы
          — понять, каким образом статика выливалась на cdn и либо как-то перенести такой же механизм в webpack, либо как-то переделать
          ну и куча всяких незаметных по началу нюансов, которые всплывают в процессе)


    1. unel
      10.11.2015 21:39

      Но используете ли вы какую-то библиотеку для immutable данных

      Immutable данные пока не используем. Вообще, хотелось использовать Immutablejs, но либа показалась нам большеватой. А пока reducer всегда работает с deep-copy стейта, изменяя его по старинке. Уведомляем об изменениях стейта всегда, а не только когда реально произошли изменения… Пока вроде не сильно проседает в производительности, ну а как просядет — впору будет задуматься и об immutable структурах.


  1. igor-petrov
    10.11.2015 22:50

    После прочтения показалось, что первым делом вам нужно избавиться от этого загадочного сборщика, который вставляет такие палки в колеса. Нашли время, готовы были перейти на bleeding edge — но в итоге из-за него написали свой велосипед (хоть и продуманный, хоть и react-подобный).


    1. unel
      11.11.2015 13:38

      Всё верно) Но сборщик оказалось заменять несколько сложней, чем внедрить что-то своё)
      Заодно в качестве бонуса немного лучше поняли, как это всё работает внутри)

      Так-то мы не исключаем переход на обычные react/redux, но на это будем смотреть уже после внедрения и обкатки вебпака)


  1. nightstalker
    11.11.2015 01:40

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


    1. unel
      11.11.2015 13:35

      ну, он просто старый и самописный)


  1. PQR
    11.11.2015 11:31

    Вам нужно пойти дальше: ClojureScript или Elm на худой конец


    1. unel
      11.11.2015 13:39

      Нууу) Скажем так: до них мы ещё не доросли ;-)


  1. EngineerSpock
    11.11.2015 12:27

    Вы WinPhone app совсем забросили, или как? Кстати, с какой-то версии стало дико неудобно в несколько кликов добавлять новое слово. Раньше можно было в один клик, а ещё оно тормозит сильно на Lumia 920, а это не самый слабый WinPhone.


    1. Rirus
      11.11.2015 15:57
      +1

      Добавьте тайл для добавления слова на рабочий стол. Это самый быстрый и удобный способ (среди всех 3х платформ) для добавления слова или просмотра перевода, сам им пользуюсь. По факту, всё приложение не запускается, открывается только нужная часть. Тайл можно добавить с экрана добавления, ну или Лев его предложить должен.

      А по поводу «не самый слабый WinPhone» — это вы MS расскажите; 1020 тормозит так, что некоторые приложения просто не хотят запускаться. Проблема в первую очередь аппаратная: приложения написанные под Windows Phone 8.1 работают на новом движке, который не оптимизирован под девайсы, изначально выпущенные с 8.0. Приложение Lingualeo реализованно как Universall App (тот, который для 8.1, где общее всё, кроме дизайна).


      1. EngineerSpock
        11.11.2015 16:21

        Ух ты, за тайл огромное спасибо! Как-то не очевидно для меня это оказалось.

        По поводу WinPhone, ну, не знаю, подавляющая часть приложений летает, вроде бы.


        1. Rirus
          11.11.2015 17:04

          Про тайл в велкоме Лев говорит, возможно пропустили, или уже просто не нажимаете на Льва :)

          А про винфон — тот же 6tag работает с перебоями. Т.е. если сравние работу приложений на *20 моделях и хотя бы на *30 (а ведь уже *50 выходят, но уже с Windows 10), то почуствуете разницу. Это примерно как сравнивать Lumia 800 и 820.