Как я мигрировал проект с Angular 1 на React

habr.png

Всем привет!

Хочу поделиться своим опытом и инструментами, которые я использовал для миграции проекта с Angular 1 на React.

TLTR: Я написал модуль, с помощью которого можно трасформировать Angular компоненты (контроллер + шаблон) в React компоненты.


Не холивара ради

В данной статье я не буду доказывать почему и какой фреймворк лучше. Да, кроме React есть Vue, уже вышел Angular 6, а еще Ember, Svelte и многие другие… В общем, хочу рассказать, как я решал поставленную задачу, надеюсь мой опыт и наработки кому-то пригодятся.


Проект

У каждого могут быть свои причины перехода на другой фреймворк/библиотеку. В моей компании основной проект был написан когда React еще пешком под стол ходил довольно давно, для этого был выбран Angular 1.x. Иногда он приносил боль (дайджест цикл, магия с вотчерами и ангуляровскими промисами), но в целом дело свое делал.

Во всех новых смежных проектах, в том числе и в мобильной версии основного проекта, используется связка Redux + React + Typescript + CSS Modules. В итоге появилась своя библиотека компонентов, стилей, все проекты строго стандартизированы, разработка новых компонентов и подпроектов ускорилась в разы.

Основной проект продолжал жить параллельно на Angular и требовал на поддержку всё больше и больше времени, потому что нельзя просто так взять и использовать готовый код приходилось решать заново уже решённые задачи, писать с нуля компоненты. Тем более на горизонте появилась перспектива объединить основную и мобильную версии проекта в один проект с адаптивной вёрсткой.

Да, есть ngReact, но превратить проект в этакого монстра Франкенштейна не было особого желания. Поэтому было принятно решение перенести проект на React для упрощения его развития и поддержки.


Что было

Основной проект


  • Проект на Angular 1.4.10, angular-ui-router, angular-ui (модальные окна, календари)
  • 60/40 — Typescript/ES2015+
  • 182 шаблона, 156 директив, 100 контроллеров
  • angular-mock + Jest для тестирования
  • LESS (включая LESS код из Bootstrap 3) + BEM

Смежные проекты и мобильная версия


  • React 15.x (Preact.js), React-router
  • 100% Typescript
  • Jest для тестирования
  • Библиотека компонентов + CSS модули

Отмечу, что большая часть всей бизнес-логики (валидаторы, отправка запросов, утилиты и т.д.) была реализована отдельно на Typescript в виде NPM-модулей, что позволяет легко переиспользовать код между проектами независимо от фреймворка.


Берёмся за дело


«Я люблю рутину и рефакторинг!» — ни один разработчик на свете

Я думаю, многие согласятся, что рефакторинг это не самое интересное занятие. Поэтому я решил частично автоматизировать этот процесс.

Даже поверхностно сравнив компоненты React и Angular, можно вывести (конечно сильно упрощённую) формулу:

React.Component = Angular Controller + Angular Directive + Angular Template;

Так получился ng2react-builder


ng2react-builder

Вы, наверное, уже поняли, что я мастер давать названия модулям. Ну ладно, не об этом…

Что умеет модуль

Лучше всего посмотреть пример из документации, а еще лучше примеры компонентов в тест-кейсах.

Мы можем скормить модулю наш шаблон и контроллер (директивы пока в пролёте), и на выходе получится собранный React компонент (React.PureComponent или React.Component) с JSX разметкой.

Без контроллера можем легко собрать простой компонент без состояния (React.StatelessComponent).

ng2react-builder Пытается преобразовать все Angular-выражения в валидные JS/JSX конструкции:


  • из ng-repeat мы получим нативный JS’ный .map() с JSX выводом

{{item.name}}
{results.slice(0, 5).map((item, index) => { return {item.name} })}


  • контент {{expression}} будет преобразован в {expression}
  • трансформация обработчиков событий и нестандартных директив:

{{item.name}}

// часть настроек для ng2rect-builder'а
directivesToTags: {
     'my-icon': {
         tagName: 'MyReactIcon',
         valueProp: 'type'
    }
}

 {
    event.preventDefault();
    selectItem(item);
}}>
    {item.name}


Ещё особо полезной вещью может стать возможность преобразования директив в вызов JS функции (сейчас поддерживаются только директивы, заданные как атрибут):



// часть настроек для ng2rect-builder'а
directivesToTextNodes: {
    myDirective: {
        callee: 'myFunc',
        calleeArguments: ['arg1']
    }
}

{myFunc(arg1, 'some.value')}

Как это работает


  • Шаблон (если он задан) разбирается через parse5, дальше работаем с HTML AST.
  • Проводим всевозможные нормализации и трансформации директив, выражений, обработчиков и т.д.
  • AST собирается в JSX шаблон
  • Контроллер (если он задан) разбирается на AST с помощью TypeScript Compiler API.
  • Контроллер преобразовывается в класс компонента, добавляется метод render с полученным JSX шаблоном
  • Общий код компонента прогоняется через Prettier
  • Готово! Берём напильник…

Почему Typescript Compiler API


  1. Контроллер может быть написан на TypeScript
  2. Мы хотим получить на выходе React компонент на TypeScript с сгенерированными интерфейсами State и Props.


Чудес не бывает

Увы. Как я не пытался.

Как я написал выше, придётся брать напильник и продолжать рефакторинг вручную, но этот модуль сэкономил мне огромную часть времени на рутинных задачах, особенно в переводе Angular шаблонов на JSX.


Что стало


  • Основной проект полностью переписан на Typescript и React
  • Стало легче переиспользовать компоненты и код с остальными проектами
  • Рефакторинг/написание новых тестов, да помогут нам Jest snapshot’ы :)


Что впереди


  • Переход с LESS + BEM на CSS-модули

Надеюсь, вам понравилось, и я помог кому-то сэкономить время в процессе рефакторинга ;)

© Habrahabr.ru