Angular. Update 13

Angular. Обновление 13

Субъективный опыт перехода с 12 на 13 версию Angular по состоянию на вторую половину декабря 2021.

Дисклеймер

Еще раз: здесь вам не гайд, а плач. Написано исключительно под впечатлением, содержит ряд субъективных суждений, не претендующих на истину в последней инстанции. Обновление устанавливалось во второй половине декабря 2021 года. Не исключено, что в тот момент, когда Вы читаете этот опус, все уже частично или полностью поменялось в лучшую (или худшую) сторону.

Лирическое вступление

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

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

Релиз Angular v.13

Тринадцатое обновление было презентовано публике в первых числах ноября, о чем незамедлительно было уведомлено в блоге Angular. Если вы еще не обновились, строго рекомендуется ознакомиться.

К слову говоря, это единственный более ли менее заслуживающий доверия источник при выполнении обновления. Все остальные публикации, как правило, представляют из себя посильные компиляции и переводы. Зачастую квалификация компиляторов оставляет желать лучшего, иногда складывается впечатление, что они вообще слабо представляют предмет. Ну да ладно, опустим особенности генерации контента в этом вашем Интернете.

Скажу больше, в этом самом блоге могут быть упомянуты вещи, не указанные на странице обновления.

Ждем-с…

Что ж, ждем, пока обновление как следует настоится и когда все нужные зависимости обновятся. Прямо как написано тут.

Как правило, я начинаю ожидание с мониторинга Angular Flex-Layout. Обычно он без особых проблем обновлялся в течение недели после релиза, но в этот раз что-то пошло не так. Хроника борьбы за живучесть обладала определенным драматизмом, однако в конечном итоге окончилась победой.

Пока команда Angular Flex-Layout остальные мои немногочисленные зависимые от Angular зависимости тоже худо-бедно подтянулись. Естественно, за исключением angular-calendar. Тот день, когда я решил воспользоваться плодами чужого труда и использовать этот пакет… Я уж не помню, как это было — то ли очередной прям горящий дедлайн, то ли просто лень, а скорее всего причина одна — безблагодатность. С тех пор я регулярно, раз в полгода говорю себе: «Это надо выпилить!». И забываю на полгода… В конечном итоге обновился даже он… Ну, по крайней мере, попытался — теперь при компиляции приложение тошнит, но оно хоть не падает…

За все, про все, а месяца с лишним как ни бывало. Angular за это время выпустил с пяток патчей, так что можно было и приступать.

Обновление

Открываем блог, руководство по обновлению и пытаемся свести воедино.

Пока, View Engine

Спасибо, было круто, но View Engine больше не поддерживается. Как человек, перешедший на Ivy еще в незапамятные времена, с трудом представляю себе логику людей, упорно продолжавших сидеть на View Engine. Им намекали, их предупреждали. Для товарищей наступило время принудительной боли, можно было бы посочувствовать, но нет.

Пока, IE

С чувством глубокого морального удовлетворения приведу цитату из блога: «We heard your feedback and worked to pave a path forward with the removal of IE11 support in Angular v13.»

В качестве эпитафии добавлю: IE, гори в аду! Если взять все человеко-часы, потраченные на поддержку браузера Internet Explorer 6, 7, 8… (далее по списку), то это будут века, наполненные болью и страданием.  Из полифилов, призванных обеспечить эту самую совместимость, собирали библиотеки. На все у IE было собственное видение, толкование и поведение. В ад, в ад…

Node.js и процесс обновления

Довольно лирики. В руководстве упомянуто: «Make sure you are using Node 12.20.0 or later».

Надеюсь, вы также пользуетесь nvm. Подобно Венечке, собирающемуся в далекие Петушки, осмотрим свои сокровища:

  • v12.18.2(LTS: Erbium), npm v.6.14.5 ⇾ current
  • v14.18.2(LTS: Fermium), npm v.6.14.15
  • v16.13.1(LTS: Gallium), npm v.8.1.2

И здесь я допустил стандартную ошибку жадности, неверное истолковав слово «later«. С одной стороны, v16.13.1 — это later. С другой стороны, прыжок через две мажорных версии npm может быть очень сильным later-ом. Тем более, что в 7 версии разработчики npm решили коренным образом пересмотреть свое отношение к peerDependencies, чем добавили не иллюзорной головной боли всем остальным.

Итак, я выбрал стильную, модную, молодежную v16.13.1 и выполнил команду:

npx @angular/cli@13 update @angular/core@13 @angular/cli@13 —force

Слепая вера во флаг —force рухнула вместе с процессом обновления. В логах было написано что-то такое, что человеческий разум был постичь не в состоянии.

Вторым неприятным сюрпризом оказалось отсутствие папки node_modules в проекте. Очевидно, команда обновления решила перед смертью избавить меня от нее.

Что ж, как и принято, после того, как все самое плохое уже произошло, копошимся по репозиториям angular и после изучения тонны задач выясняем, что таки да:

  • обновление может убивать папку node_modules;
  • Node.js v16 поддерживается только cli-dev, но это не точно.

Дальше немного мистики. Понятно, что v16.13.1 не катит, переключаюсь на v14.18.2, откатываю git, делаю npm install. Сюрприз: установка пакетов наглухо виснет при попытке установить какую-то древнюю версию rxjs, очевидно используемую в каком-то пакете. Вот уж нежданчик так нежданчик! Переключаюсь на v12.18.2, инсталлирую — все в порядке. Мы опять в начале пути.

Переключаюсь на v14.18.2, копирую от греха подальше папку node_modules, запускаю обновление — слава богам, готово. Хотя, чисто субъективно, обновлялось очень долго.

Далее по плану:

ng update @angular/cdk @angular/material @angular/flex-layout —force —allow-dirty

ng update @angular-eslint/schematics @angular-eslint/builder @angular-eslint/eslint-plugin @angular-eslint/eslint-plugin-template @angular-eslint/template-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-jsdoc eslint-plugin-prefer-arrow —force —allow-dirty

Все прошло нормально, но опять таки долго.

Пытаемся запустить — все красное. Это предсказуемо, будем разбираться.

TSLint

Не секрет, что TSLint, изначально выбранный Angular в качестве линтера, был объявлен deprecated еще пару лет назад. Всех, кто так и не перешел на ESLint, в процессе обновления ожидает сюрприз: секция lint файла angular.json магическим образом исчезает. При этом если секция с таким же именем содержит @angular-eslint, то она остается на месте.

О такой особенности обновления никто предупредить не соизволил.

Изменение Angular Package Format (APF)

Про это было объявлено в блоге. Всем счастливым обладателям собственных библиотек придется основательно залипнуть здесь.  Нудное приведение package.json к стандартам Angular удовольствия не доставило ни разу.

Изменение APi компонентов

Проявилось исключительно в выпиливании ComponentFactoryResolver. Но об этом предупреждали еще в незапамятные времена, поэтому меня не коснулось. Детали можно посмотреть в блоге.

Кэширование сборки

В папку проекта объявилась директория .angular/cache, служащая (внезапно!) для кэширования. Обещанного 68% прироста скорости билдов особо не заметил. Зато приметил другое, совсем не порадовавшее: при компиляции вылетает ошибка в scss, поправил, а она по-прежнему вылетает. Очистил папку кэша — ошибка ушла, но осадочек остался. Потом аналогичная ситуация повторилась при удалении компонента. В общем, не так там все хорошо, с этим кэшем.

На всякий случай отключил его при production (есть такая возможность в angular.json) и добавил скрипт rm -rf .angular/cache, чтоб не возиться.

Typescript

Несмотря на заявление на сайте обновления «Angular now uses TypeScript 4.4…», обнаружил, что установлена версия 4.5.4. Очевидно, это из-за того, что за месяц релиз добрался аж до версии 13.1. Версией Typescript 4.5 возмущались некоторые компоненты ESLint, ну да это дело житейское.

Большинство возникших ошибок компиляции касалось введенного в v4.3 override для наследуемых методов. Вкратце: теперь при наследовании метода нужно добавлять волшебное слово override… Хм-м-м-м… Где-то я это уже встречал. Java, не? С#, не? В общем, есть два варианта: либо лазать по всему коду и добавлять это самое override, либо в tsconfig.json добавить «noImplicitOverride»: true.

Начитавшись всяких офигенно компетентных мнений, которых хором прославляли это самое override, я добавил его в несчетное количество мест. Стал ли код от этого лучше? Понятнее? Читабельнее? Не уверен… В общем, лучше бы, наверное, опцию добавил вместо очередной порции синтаксического сахара.

Одним override дело не обошлось…

interface Foo {
  [key: string]: string;
}
const bar: Foo = {};

// раньше вот так работало:
bar.prop = 'one';

// теперь ругается люто и хочет вот так:
bar['prop'] = 'one';

Оно, конечно, имеет под собой какое-то основание. По крайней мере больше, чем override… 

Правда, пришлось править настройки eslint, который после исправления принимался канючить насчет рекомендуемой dot-notation.

Angular Material

Ну что сказать, эти ребята умают нагадить в тапки! 

Пожалуй, приведу цитату из блога: «To get a better idea of how these changes impact components, have a look at the adjustments we’ve made to the touch target sizes for components like checkbox and radio button.»

Спасибо, отцы родные! Что это значит на практике: все группы checkbox-ов и radio, стилей которых не касалась рука человека, теперь стали выглядеть… чуть-чуть иначе. И занимать чуть-чуть больше места… Раза эдак в три… Может быть, это и ловится какими-то особо хитрыми тестами, но заверяю вас — я таких тестов не писал. В результате пришлось, сжав зубы и приговаривая пословицы с поговорками, лазать по нескончаемой веренице форм и устранять образовавшиеся новообразования. 

SCSS

Вот тут даже не знаю, смеяться или плакать.

// так было до эпохи исторического материализма, при этом ~ (тильда) означала, что все отсчитывается от node_modules
@import '~@angular/material' as mat;

// потом нам объяснили, что import — это отстой, и мы начали писать так:
// что характерно, тильда никуда не делась
@use '~@angular/material' as mat;

// С 13 обновления надо писать так и никак иначе:
@use '@angular/material' as mat;

Кому мешала эта несчастная тильда? Ну уж явно не мне.

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

Лечится это добавлением в angular.json:

...
build: {
...
"stylePreprocessorOptions": {
              "includePaths": [
                "node_modules"
              ]
            },
...
}

Кому мешало то, что работает? В чем смысл? Загадка… JetBrains уже ломает себе голову, как поддержать очередные смелые нововведения бригады улучшителей, народ, приглушенно матерясь, выпиливает несчастные тильды…

И это еще не все. Думается, в связи с пересмотром Angular Package Format, внезапно возник следующий кейс.

Итак, вы хотите импортировать из стилей Angular Material нечто. Допустим, elevation. И вы делаете это так:

@use '@angular/material/core/style/elevation' as elevation;

foo {
   @include elevation(8);
}

При этом ваша IDE находит по указанной ссылке источник и вполне довольна собой. Но компиляция падает с ошибкой: «Не могу найти…». Какого хрена? Оно же там есть!

Ан нет. Теперь надо пойти в node_modules/@angular/material/package.json, найти секцию exports и смотреть, а что же нам все-таки отдают. И выяснить, что для экспорта пригодны только файлы _index.scss и _theming.scss. Вот все, что в них форвардится, пригодно к импорту, притом только через эти файлы, то есть писать надо так:

@use '@angular/material' as mat;

foo {
   @include mat.elevation(8);
}

Ну и если вам уж совсем повезло, то _index.scss и _theming.scss могут вовсе не форвардить того, что вы так успешно ранее импортировали.

RxJS

Как человек, помнящий еще лапшу callback-ов в первых версиях Node.js, я очень люблю RxJS. 

Но есть нюанс. Бесконечное стремление к совершенству команды разработчиков этого прекрасного продукта привело к тому, что в этих ваших Интернетах уже невозможно найти актуальный урок по RxJS — либо он вообще не заработает, либо добрую половину методов будут deprecated. Я, конечно, понимаю, что мажорная версия не гарантирует обратной совместимости, но кто сказал, что она гарантирует полную не совместимость? За время существования библиотеки каждый из методов переписывался по нескольку раз, постоянно меняются сигнатуры методов… И конца этому шоу не предвидится.

В 13 обновлении RxJS прыгнул к версии 7.4.0, причем установить ее надо руками: npm install — save rxjs@7.4.0.

А дальше хоть через анализатор кода, хоть через eslint искать свеженькие deprecated от RxJS. И они там будут.

Из запомнившегося. Теперь подписки (subscribe) принимают либо одни callback (next), либо объект. Попробую показать:

// так можно: 
of([1,2,3]).subscribe((v) => console.info(v));

// так тоже
of([1,2,3]).subscribe({
    next: (v: number) => console.log(v),
    error: (e: any) => console.error(e),
    complete: () => console.info('complete') 
})

// и так тоже
of([1,2,3]).subscribe({
    complete: () => console.info('complete') 
})

// так уже нельзя:
of([1,2,3]).subscribe(
    (v: number) => console.log(v),
    (e: any) => console.error(e),
)

Аналогичная беда приключилась и с оператором tap. Теперь там тоже в качестве аргумента нужен объект.

Недоброе приключилось с моей любимой связкой publish(), refCount():

// было:
const tick$ = timer(1_000).pipe(
  publish(),
  refCount()
);

// стало:
const tick$ = timer(1_000).pipe(
  share({
    resetOnError: false,
    resetOnComplete: false,
    resetOnRefCountZero: false,
  })
);

Выпилили toPromise()… Вместо него вообще предлагается не понятно что…

В общем, копипастить нет смысла. Весь этот ужас лежит здесь: Breaking Changes in Version 7

Вместо заключения

В общем и целом, обновление прошло штатно…

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