Трудности перехода

 Денис Омельков

 

Конфур

Екатеринбург 2017

Контур.Стафф

Технологии

  • Single page application
  • TypeScript
  • AngularJS 1.x
  • Grunt + scripts

Масштаб SPA

  • 117K строк кода
  • 440 компонентов
  • 3.2MB (minified)
  • 20 chunks
  • ES6 модули
  • Производительность
  • Новый сборщик

Мотивация обновления

Angular 2 

Angular 4

План миграции

  • Написать конвертер
  • Сконвертировать
  • ???
  • PROFIT!

План миграции

Правила конвертирования 

  • Отдельно правила для кода и для шаблонов
  • Всего 29 правил

Правила конвертирования

преобразование манифеста компонента

{
  "properties": "text",
  "events": "click",
  "template": "./button.html",
  "type": "component",
  "ctrl": "staff.components.ButtonComponent"
}
@Component({
  selector: "my-button",
  templateUrl: "./button.html",
}) export class MyButtonComponent {
    @Input() text: string;
    @Output() click = new EventEmitter<any>();
}

Правила конвертирования 

initializeComponent() 

destroyComponent() 
		
prop_change() 
  • рефакторинг методов api и lifecycle hooks
ngOnInit()

ngOnDestroy()
		
ngOnChanges(changes){
if(changes["prop"]){
    this.prop_change(...)
    ...
}

Конвертер

  • Обход каталога
  • Применение трансформаций
  • Сохранение копии исходного файла

Конвертер

Подходы

  • Модификации на основе синтаксического дерева AST (TypeScript API + parse5)
  • Регулярные выражения

Работа с AST

  • astexplorer.net
  • jscodeshift
  • API TypeScript не позволяет сериализовать синтаксическое дерево в TS код

Решение

Замена в исходных файлах

Проблемы конвертации

TypeScript

Проблемы конвертации

HTML

  • Шаблоны Angular не являются валидным HTML

Решение

Настройка или модификация парсера

Использование старых версий

Пример преобразования с AST

for (let pk of obj.properties) {
  if (pk.kind == SyntaxKind.PropertyAssignment) {
      if (prop.name.text == 'properties') {
        inputProperties = prop.value.split(' ');
      }
   }
} 
if (member.kind == SyntaxKind.PropertyDeclaration 
    && inputProperties.includes(member.name)){
    replacements.push({
            start: offset,
            end: offset,
            replacement: ' @Input()'
    });
}

Пример преобразования

с AST (шаблон)

for(let name in dom.attribs){
    if(dom.attribs.hasOwnProperty(name) && name == 'ng-repeat'){
        dom.attribs['*ngFor'] = 'let ' + (dom.attribs[name])
            .replace(' in ', ' of ')
            .replace('track by', '; trackBy:');
        delete dom.attribs[name];
    }
}

Ручная проверка и рефакторинг

  • Отсутствующие API
  • Сильно отличающиеся API
  • Давнокод
  • Сравнение со старой версией

Грабли

Роутер стал переиспользовать компоненты при смене маршрута

Старые компоненты не реагируют на изменение состояния

Сборка на webpack

Сложно описать зависимости

Сборка на webpack

Проблема вложенного require.ensure

require.ensure([], () => {
    require('./A');
}, "A");

require.ensure([], () => {
    require('./B');
}, "B");

require.ensure([], () => {
    require('./A');
    require.ensure([], () => {
        require('./B');
        require.ensure([], () => {
            let C = require('./C');
            C.C();
        }, "C")
    }, "B");
}, "A");

Chunk A

Module A

Chunk B

Module B

Chunk C

Module A

Module C

commonsChunkPlugin

Сборка на webpack

Кеширование между билдами

v 1.0

 

Chunk Alpha

  • Module A (id1)
  • Module AA (id 2)

 

Chunk Beta

  • Module B (id 3)

 

Chunk Gamma

  • Module C (id 4)

v 1.1

 

Chunk Alpha

  • Module A (id1)

 

Chunk Beta

  • Module B (id 2)

 

Chunk Gamma

  • Module C (id 3)

Сборка на webpack

Кеширование между билдами

  • [name][chunkhash].chunk.js
  • HashedModuleIdsPlugin
  • ChunkManifestPlugin

v 1.2

 

Chunk Alpha

Module A (id "8U58")

 

Chunk Beta

Module B (id "XCH3")

 

Chunk Gamma

Module C (id "OPJ6")

Сборка на webpack

Кеширование между билдами

Идентификаторы конкретных экспортов  тоже могут меняться

При этом хеш чанков остается прежним

Сборка на webpack

диагностика сборки

Webpack analyze tool

webpack.github.io/analyse

  • Фактические зависимости между чанками
  • Дублирование кода в разных чанках
  • Причины включения модуля

webpack-runtime-analyzer

Сборка на webpack

Webpack analyze tool

Сравнение версий

Размер бандла

Сравнение версий

Bootstrap

Сравнение версий

Скорость рендера

Сравнение версий

Продакшн сборка

Свой сборщик:

3 минуты, 400 Мб ОЗУ

Webpack:

10 минут, до 3 Гб ОЗУ

Сборка шаблонов в JS на сервере, минификация

Трудозатраты

  • около 80 чел/дней
  • примерно 4 месяца
  • 1,5 человека

Выводы

  • Повысили производительность
  • Отрефакторили код
  • Cовместимость с новыми библиотеками и технологиями  (RxJS, ES6 modules)
  • Поддержка со стороны IDE
  • Проверка типов еще и в шаблонах

+

  • Проблемы обновления состояния
  • Потеряли hot-reload
  • Время сборки увеличилось
  • Увеличился размер приложения
  • Проблемы с кешированием

Давнокод есть?

А если найду?

Спасибо за внимание!

omelkov@skbkontur.ru

@apocalyp_sys

bit.ly/Konfur17Port

Ссылки

Конфур лето 2017. Трудности перехода

By Denis Omelkov

Конфур лето 2017. Трудности перехода

Чтобы приложение не превратилось в кучу давнокода, а во фронтенде это может произойти невероятно быстро, его нужно рефакторить и актуализировать. Иногда этот процесс вынуждает принимать экстремальные меры и переезжать на новый фреймворк. Мы расскажем про миграцию с Angular 1, какие инструменты и подходы использовали, с какими проблемами столкнулись. Сравним старую и новую версии приложения в контексте удобства разработки, сложности сборки и производительности.

  • 624