Как мы знаем, рекомендуется всегда обновлять все зависимости в нашем проекте, по крайней мере, для наиболее важных из них. Это способ поддерживать наш проект с целью:

  • имея больше функций из этих библиотек, которые мы хотим использовать
  • ускорение проекта во время сборки
  • устранение несовместимости между разными версиями тех, которые могут вызвать проблемы с нашими приложениями

Эта статья будет довольно длинной, поэтому, если вы знакомы с современными библиотеками JavaScript, пропустите приведенный ниже контекстный раздел.

Контекст

В Fluo наша прикладная кодовая база, по сути, представляет собой JavaScript и живет внутри монорепозитория (подход, рекомендуемый Google, Facebook…). Как и в случае с множеством других стартапов, потребности бизнеса менялись быстро, и нам иногда было трудно успевать за развитием основных библиотек, которые мы использовали чаще всего.

В конце 2017 года наш стек состоял из:

  • React 15.2.1 и React Native 0.29.0: на момент написания этой статьи React 16 был выпущен довольно давно с некоторыми расширенными функциями и улучшением производительности. React Native также очень быстро эволюционировал до версии 0.54.0 и начал иметь некоторую популярность в мобильной разработке.
  • Поток 0.27.0: статически типизированная проверка нашего кода JavaScript и совместное использование бизнес-типов между серверной частью и веб-интерфейсом. Flow помогает нам избегать распространенных ошибок типов JavaScript, представлять нашу бизнес-логику по типам и при необходимости реорганизовывать наш код.
  • Jest 16: мы использовали только бегущую часть Jest в сочетании с утверждениями Chai, потому что мы оценили синтаксис Chai более гибким.
  • Webpack 1.13.1: наиболее распространенный инструмент для сборки кодовой базы JavaScript с богатой экосистемой плагинов. Последняя версия Webpack обещает значительно улучшенное время сборки (как описано здесь).
  • Babel 6: у нас есть множество плагинов babel для обработки всех типов возможных полифиллов в JavaScript. Каждый раз, когда мы начинаем настраивать новую службу JavaScript, возникает много путаницы в том, какие полифилы нам понадобятся.

Эти библиотеки совместно используются проектами внутри нашего монорепозитория, и команда согласилась, что пора позаботиться об этих устаревших зависимостях.

История Flow, React и React Native

Чего мы пытаемся достичь?

Как описано выше, в настоящее время мы используем очень старую версию Flow (0.27.0). Основная проблема в том, что это убивает нашу повседневную продуктивность из-за своей медлительности. Было замечено, что мы используем версию Flow, которая поставляется с нашей версией React Native, и вся кодовая база JavaScript проверяется с помощью этой версии Flow. Итак, чтобы обновить Flow, нам пришлось обновить React Native, React и все библиотеки, которые имеют их как одноранговые зависимости.

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

Стратегия

Наша первая цель - иметь возможность перейти на React Native 0.48.4 и Flow 0.49.1 по двум причинам:

При обновлении Flow предупреждения кода можно разделить на две группы:

  • Предупреждения, которые можно исправить немедленно, не нарушая совместимость с текущей версией Flow.
  • Предупреждения, которые нельзя исправить простым способом, поскольку для исправления требуются функции, несовместимые с текущей версией Flow.

В первом случае это не сложно, исправление можно применить сразу в нашей существующей кодовой базе.

Со вторым семейством предупреждений становится немного сложнее. Поскольку исправление нельзя внедрить в существующие исходные файлы JavaScript, хитрость заключается в дублировании исходных файлов.

Допустим, нам нужно исправить файл с именем somefile.js, используя новый синтаксис, неизвестный Flow 0.27. Затем мы создаем копию этого файла с именем somefile.nextflow.js и используем этот новый файл для записи исправления.
У нас все еще есть проблема, потому что эти новые файлы *.nextflow.js не используются остальной частью кодовой базы, они не охватываются могут появиться наши тесты и, следовательно, скрытые ошибки. Вот почему мы будем использовать два сценария:

  • upgrade-flow.sh: обновите зависимости в package.json и удалите суффикс nextflow для всех *.nextflow.js файлов. Допустим, если у нас есть файлы somefile.js и somefile.nextflow.js, после выполнения этого сценария мы получим файл somefile.oldflow.js и файл somefile.js. Файлы с суффиксом oldflow.js являются резервными версиями кода для Flow 0.27. oldflow.js файлы актуальны только тогда, когда есть исправление, связанное с изменением прерывания потока. Ниже приведен пример:
yarn add [email protected] [email protected]
yarn add [email protected] react-native-deprecated-custom-components [email protected] [email protected] 
/*We had many dependencies on React and React Native in our package.json*/
yarn add -D [email protected] && cp -f .flowconfig__upgrade .flowconfig
all_nextflow_files=$(find "$PWD" -type f -name "*nextflow\.js")
for nextflow_file in $all_nextflow_files
do
   file=$(echo $nextflow_file | sed s/\.nextflow//g)
   oldflow_file=$(echo $nextflow_file | sed s/nextflow/oldflow/g)
   if [[ -f $file ]]; then
      mv $file $oldflow_file
      mv $nextflow_file $file
   fi
done
  • reset-flow.sh: сбросьте зависимости и переименуйте файлы js, чтобы использовать Flow 0.27. Это означает, что если у нас есть файл somefile.oldflow.js и somefile.js, reset-flow.sh переименует somefile.js в somefile.nextflow.js и somefile.oldflow.js в somefile.js. Таким образом мы не потеряем никаких корректирующих изменений, сделанных в нашей кодовой базе :). Ниже приведен пример reset-flow.sh:
rm -rf node_modules
git checkout package.json .flowconfig yarn.lock && yarn
all_oldflow_files=$(find "$PWD" -type f -name "*oldflow\.js")

for oldflow_file in $all_oldflow_files
do
   file=$(echo $oldflow_file | sed s/\.oldflow//g)
   if [[ -f $oldflow_file ]]; then
      nextflow_file=$(echo $file | sed s/\.js/.nextflow.js/g)
      cat $file > $nextflow_file
      cat $oldflow_file > $file
      rm $oldflow_file
   fi
done
  • не забудьте настроить Flow так, чтобы он игнорировал все файлы *.nextflow.js и *.oldflow.js после выполнения reset-flow.sh

Почему мы просто не используем ветку git?

Мы могли бы просто применить все изменения к новой ветке git. Преимущество было бы в том, что мы были бы уверены в том, что избегаем внесения ошибок в производственную среду. Однако отслеживать все изменения и объединять две ветки будет непросто. Вместо этого наша стратегия следует методу ветвление по абстракции, который довольно хорошо работает в нашем контексте монорепозитория.

Повседневные события

Мы продолжаем работать, как и раньше, только помните о файлах *.nextflow.js, они должны синхронизироваться со своими js-близнецами. Примените к ним те же изменения!

Допустим, вы работаете над модулем abc, а там уже есть abs.js и abc.nextflow.js. Нам нужно применить исправление или добавить функцию в abc. Вам нужно будет изменить abs.js и применить изменение к abs.nextflow.js. Чтобы убедиться, что вы ничего не сломали, рекомендуется следующий рабочий процесс:

  1. Измените оба файла
  2. Запустите тесты и убедитесь, что все тесты зеленые
  3. Применить upgrade-flow скрипт
  4. flow --show-all-errors и исправьте ошибки потока
  5. Запустите тесты и убедитесь, что все тесты зеленые
  6. Применить reset-flow скрипт
  7. Зафиксировать изменения и все *.nextflow.js файлы

Инструменты

У нас есть два описанных выше сценария и сборка Jenkins, которая запускается после каждого нажатия, чтобы убедиться, что не обнаружены новые ошибки Flow и не сломаны тесты.

Советы и уловки

  • Мы постепенно переносим нашу кодовую базу между разными версиями React и React Native, чтобы не вносить много критических изменений из этих двух библиотек и их соответствующих зависимостей одновременно. На каждом этапе обновления мы не боялись обновлять все JS-сервисы на этапе и в производстве немедленно, чтобы выявлять регрессии как можно быстрее.
  • Команда Facebook предоставляет codemod для миграции на React, вам может потребоваться руководство по миграции, если вам нужно перейти на React Router v4.
  • Будьте осторожны при обновлении React Native, не читая внимательно его критические изменения. Чтобы наше мобильное приложение не прекращало работу, мы рекомендуем иметь задание Jenkins, которое запускается fastlane gym каждый раз, когда мы выполняем этап миграции.
  • Пожалуйста, перед обновлением подумайте об одной из навигационных библиотек для React Native. На данный момент мы используем прокладку react-native-deprecated-custom-components вместо навигатора. На момент написания React-Navigation (официальная библиотека маршрутизаторов, рекомендованная командой React Native) выпустила свою версию 1.0. Мы могли бы рассмотреть возможность его использования и полностью удалить навигатор.
  • У вас может быть Enzyme для тестов во фронтенде. По какой-то причине React Native 0.48.4 имеет React 16.0.0-alpha.12 как одноранговую зависимость. Это может повлиять на некоторые из ваших тестов Enzyme, поскольку Enzyme не поддерживает альфа-версии React (см. обсуждение здесь и здесь). В нашем случае мы использовали React 16.0.0 напрямую, мы получили некоторые предупреждения от yarn, но это не имеет большого значения.
  • Произошли важные критические изменения с Flow 0.27 на 0.28, мы думаем, что это неприятное изменение для нас, так как у нас не было четких несвязных объединений, реализованных в начале использования Flow.
  • Не забудьте адаптировать .flowconfig к тому, что есть в проекте-примере React Native, созданном с помощью react native cli в нужной версии.
  • Убедитесь, что все файлы потокового типа были обновлены при выполнении шага инкрементной миграции.
  • Если вы используете react-native-web, следите за этой интересной проблемой, потому что при обновлении этой библиотеки может возникнуть много ошибок Flow.
  • Я опубликовал его репозиторий на github, чтобы показать некоторые уловки при работе с Flow.

Результат

Сегодня мы завершаем всю миграцию для React (16.2.0), React Native (0.53.3) и Flow (0.65.0). Мы рады, что Flow обеспечивает более высокую скорость проверки типов, но мы нашли эту длительную миграцию особенно болезненной, потому что все проекты имеют эти общие зависимости (в монорепозитории), и нам нужно убедиться, что не было введено никаких регрессий. Имея большую базу кода, которая какое-то время отставала от эволюции React и Flow, стоимость переноса всего этого немалая. Поэтому мы будем продолжать обновлять нашу зависимость более регулярно, чтобы в будущем не иметь серьезных критических изменений.

А как насчет Jest и Webpack?

Jest - один из тех инструментов, на обновление которых мы не прикладывали никаких усилий. Мы решили удалить утверждения Chai, чтобы вернуться к утверждениям Jest.

Зачем удалять утверждения Чай?

  • Наличие Chai для утверждений мешает нам правильно печатать функции Jest в Flow.
  • Когда дело доходит до сравнения двух больших объектов, утверждение Jest дает более четкую строку ошибки, чем утверждение Chai.

Советы и рекомендации при обновлении Jest

  • Используйте jest-codemod, чтобы преобразовать все утверждения Chai в утверждения Jest.
  • jest-codemod не идеален. Это требует некоторой ручной проверки. Мы нашли эту статью чрезвычайно полезной, когда дело доходит до переноса некоторых тестовых программ на Jest. Мы можем использовать Flow или перезапустить все тесты, чтобы увидеть, что-то не работает.
  • Если вы используете react-native-web, перейдите по этой ссылке, чтобы узнать, как протестировать его компоненты.

Наконец-то Webpack

При обновлении webpack до последней версии 3 мы могли бы воспользоваться двумя важными функциями:

  • Встряхивание дерева из webpack 2. Чтобы эта функция была недоступна, нам нужно запретить webpack связывать нашу кодовую базу с CommonJS (по умолчанию), но с модулями ES6.
  • Поднятие области действия из веб-пакета 3. Чтобы отключить эту функцию, используйте ModuleConcatenationPlugin из веб-пакета.

Это руководство по миграции необходимо для перехода с webpack 1 на 2. От webpack 2 к webpack 3 не нужно тратить никаких усилий. Обратите внимание, что после этого обновления мы можем использовать React Storybook, который, похоже, поддерживает только версию webpack ≥ 2.

Выводы

Большое спасибо команде Fluo за то, что это обновление стало реальностью. Нам кажется, что мы выплатили некоторые технические долги и теперь больше доверяем нашей кодовой базе. Эти библиотеки, указанные выше, являются существующими проектами, и они развиваются так быстро, что мы должны следить за ними каждый день.

Наконец, к моменту написания этой статьи мы закончили обновление наших основных зависимостей и ждем следующего вызова от Babel 7.