Трудности перехода
Вячеслав Бухарин, Денис Омельков
СКБ Контур
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,169