Трудности перехода
Денис Омельков
Конфур
Екатеринбург 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
Ссылки
- astexplorer.net – браузер синтаксических деревьев
- jscodeshift – конвертер кода
- parse5 – парсер HTML
- github issue о сериализации TypeScript AST в TS код
- github issue о зависимостях между чанками в webpack
- Гайд по работе с браузерным кешированием в webpack
- Анализатор json логов билда webpack
- Анализатор билдов webpack в виде плагина
- Свой сборщик для AngularJS
- Гайд по Ahead of Time сборке шаблонов в Angular
- Доклад "Меняем JavaScript с помощью JavaScript", Павел Волокитин
- Доклад "Observable в JavaScript?", Теймураз Хазамов
Конфур лето 2017. Трудности перехода
By Denis Omelkov
Конфур лето 2017. Трудности перехода
Чтобы приложение не превратилось в кучу давнокода, а во фронтенде это может произойти невероятно быстро, его нужно рефакторить и актуализировать. Иногда этот процесс вынуждает принимать экстремальные меры и переезжать на новый фреймворк. Мы расскажем про миграцию с Angular 1, какие инструменты и подходы использовали, с какими проблемами столкнулись. Сравним старую и новую версии приложения в контексте удобства разработки, сложности сборки и производительности.
- 624