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

Вячеслав Бухарин, Денис Омельков

СКБ Контур

UralJS

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

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

Технологии

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

Дерево компонентов

Components

module staff.components {
    export class ButtonComponent {

        text: string; // prop
        click: IEventEmitter; // event

        initializeComponent() { 
            // do stuff
        }

    }
}
{
  "properties": "text",
  "events": "click",
  "template": "./button.html",
  "type": "component",
  "ctrl": "staff.components.ButtonComponent"
}

Масштаб SPA

  • 117K строк кода
  • 440 компонентов
  • 3.2MB (minified)
  • 20 chunks

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(...)
    ...
}

Конвертер

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

Конвертер

Подходы

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

AST

if (params.id) url += getRouteId(params.id);

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') {
        // будем удалять properties из декоратора
        replacements.push({
	    start: prop.pos,
            end: prop.end + 1,
	    replacement: ''
	});
        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")

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

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

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

Bootstrap

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

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

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

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

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

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

Webpack + Ng2

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

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

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

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

Выводы

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

+

  • Снизилось удобство разработки
  • Время сборки увеличилось
  • Проблемы с кешированием

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

omelkov@skbkontur.ru

buhv@skbkontur.ru

@apocalyp_sys

tech.skbkontur.ru

Text

UralJS3

By Denis Omelkov

UralJS3

  • 1,082