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

в 5:43, , рубрики: angular, javascript, ReactJS, TypeScript, переход с angular на react

Всем привет!

Хочу поделиться своим опытом и инструментами, которые я использовал для миграции проекта с 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 выводом

<!-- было -->
<div>
    <span ng-repeat="item in list as | limitTo:5  as results">{{item.name}}</span>
</div>

<!-- стало -->
<div>
    {results.slice(0, 5).map((item, index) => {
        return <span key={`child-${ index }`}>{item.name}</span>
    })}
</div>

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

<!-- было -->
<a ng-click="$event.preventDefault(); selectItem(item)">{{item.name}}</a>
<my-icon="calendar"><my-icon/>

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

<!-- стало -->
<a onClick={(event) => {
    event.preventDefault();
    selectItem(item);
}}>
    {item.name}
</a>
<MyReactIcon type="calendar"/>

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

<!-- было -->
<span my-directive="some.value"></span>

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

<!-- стало -->
<span>{myFunc(arg1, 'some.value')}</span>

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

  • Шаблон (если он задан) разбирается через 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-модули

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

Автор: webschik

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js