Русский
Русский
English
Статистика
Реклама

Angulardart

Зоны в Dart большой брат следит за тобой

16.07.2020 12:18:27 | Автор: admin

Привет! Меня зовут Дима, я frontend-разработчик в компании Wrike. Клиентскую часть проекта мы пишем на Dart, однако работать с асинхронными операциями нам приходится не меньше, чем на других технологиях. Зоны один из удобных инструментов, который Dart для этого предоставляет. Недавно я начал разбирать эту тему, а сегодня планирую показать оставшиеся у меня примеры применения зон и неочевидные особенности их использования. Как обещал посмотрим на AngularDart.


Если хотите разобраться с базовыми возможностями зон, прочитайте мою первую статью.


image


NgZone и оптимизация процесса change detection


Представим рабочую ситуацию: на ревью спринта вы рассказываете про новую фичу, уверенно обходите стороной известные баги и показываете функциональность с лучшей стороны. Но через пару кликов самописный счетчик производительности фреймворка показывает over 9000 попыток перерисовать интерфейс. И это только за секунду!


После окончания ревью возникает непреодолимое желание исправить ситуацию. Скорее всего, первой идеей будет обмануть счетчик. Для этого надо разобраться, как он работает. Идем в код. Вероятно, мы увидим там эти строки:


class StandardPerformanceCounter {  final NgZone _zone;  StandardPerformanceCounter(this._zone) {    _zone.onMicrotaskEmpty.listen(_countPerformance);  }  // ...}

После дальнейшего исследования становится понятно, что это неспроста: Angular использует именно стрим onMicrotaskEmpty в корне каждого приложения для того, чтобы на каждый его event автоматически запускать процесс change detection:


class ApplicationRef extends ChangeDetectionHost {  ApplicationRef._(    this._ngZone, // ...  ) {    // ...    _onMicroSub = _ngZone.onMicrotaskEmpty.listen((_) {      _ngZone.runGuarded(tick);    });  }  // Start change detection  void tick() {    _changeDetectors.forEach((detector) {      detector.detectChanges();    });  }  // ...}

Похоже, надо разбираться с тем, что же такое NgZone, как она работает и фиксить приложение как положено. Смотрим под капот.


NgZone это не зона, а обертка над двумя другими зонами внешней (зона, в которой стартовало Angular приложение) и внутренней (зона, которую создал Angular и внутри которой он автоматически запускает все операции приложения). Обе сохраняются на этапе создания NgZone:


class NgZone {  NgZone._() {    _outerZone = Zone.current; // Save reference to current zone    _innerZone = _createInnerZone(      Zone.current,      handleUncaughtError: _onErrorWithoutLongStackTrace,    );  }  // Create Angular zone  Zone _createInnerZone(    Zone zone, // ...  ) {    return zone.fork(      specification: ZoneSpecification(        scheduleMicrotask: _scheduleMicrotask,        run: _run,        runUnary: _runUnary,        runBinary: _runBinary,        handleUncaughtError: handleUncaughtError,        createTimer: _createTimer,      ),      zoneValues: {_thisZoneKey: true, _anyZoneKey: true},    );  }  // ...}

Внутренняя зона принимает на себя много работы и использует разные особенности зон.


Для начала поверхностно напомню, зачем нужен change detection.



Так может выглядеть простая древовидная структура компонентов


Для построения интерфейса Angular использует компоненты, которые выстраиваются в древовидную структуру. В обычной ситуации после старта приложения компоненты гидрируются данными, выстраивают DOM дерево, ждут и сохраняют изменения в своих данных. При это сам Angular не узнает о существовании этих изменений тотчас: в удобные для него моменты он запускает процесс обхода дерева компонентов от корня по всем потенциально затронутым нодам в поисках измененных данных change detection. Если изменения произошли, то фреймворк запускает обновление соответствующего DOM поддерева.


Источников у этих изменений может быть море пользовательские события, онлайн нотификации, временные отсечки. Любой из них может повлиять на интерфейс, а значит надо проверить, не изменилось ли что-то в данных компонентов и не надо ли обновить интерфейс. Отсюда растет первая задача отследить все события.


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


Вспомним про event loop браузера:



Эту схему я позаимствовал из крутого доклада Джейка Арчибальда об event loop


Слева находится секция, в которой будет выполняться какой-то таск, а справа секция, в которой будут по очереди выполняться сначала запланированные с помощью requestAnimationFrame скрипты, потом произойдет калькуляция стилей, калькуляция лейаута и отрисовка. Скриптовые задачи мы можем выполнять только в желтых секциях.


А теперь попробуем залезть в голову к авторам Angular и понять, когда лучше всего выполнять detectChanges.


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


Интересно было бы отслеживать изменения в рамках requestAnimationFrame, но тут мне пришел в голову целый набор но:


  • Change detection на большом приложении может быть довольно долгим, из-за этого может вылететь много фреймов.
  • Скрипты, выполняемые в requestAnimationFrame, также могут запланировать микротаски, которые будут запускаться сразу же после выполнения скрипта и перед отрисовкой, последствия мы уже обсуждали.
  • Интерфейс после change detection может быть не полностью стабилен. Есть риск, что пользователь увидит незапланированную анимацию изменения интерфейса вместо ожидаемого конечного результата.

Остается еще один вариант запускать detectChanges после того, как будет выполнен скрипт и все его микротаски, если они есть. Это задача номер два.


Получается, что для магии Angular было бы неплохо:


  • Ловить все возможные пользовательские события.
  • Запускать change detection после того, как закончится выполнение скриптов в стеке и закончат выполнение все запланированные на тот момент микротаски.

Ловим все возможные пользовательские события. С этим прекрасно справляется та самая innerZone.


Посмотрим на нее еще раз:


class NgZone {  // ...  // Create Angular zone  Zone _createInnerZone(    Zone zone, // ...  ) {    return zone.fork(      specification: ZoneSpecification(        scheduleMicrotask: _scheduleMicrotask,        run: _run,        runUnary: _runUnary,        runBinary: _runBinary,        handleUncaughtError: handleUncaughtError,        createTimer: _createTimer,      ),      zoneValues: {_thisZoneKey: true, _anyZoneKey: true},    );  }  // ...}

В предыдущей статье мы уже разбирали, что Future выполняет свой коллбек в той зоне, в которой он был создан. Так как Angular при старте пытается создавать и выполнять все в своей внутренней зоне, то после комплита Future задача выполняется с помощью хендлера _run.


Вот как он выглядит:


class NgZone {  // ...  R _run<R>(Zone self, ZoneDelegate parent, Zone zone, R fn()) {    return parent.run(zone, () {      try {        _nesting++; // Count nested zone calls        if (_isStable) {          _isStable = false; // Set view may change          //         }        return fn();      } finally {        _nesting--;        _checkStable(); // Check we can try to start change detection      }    });  }  // ...}

C помощью семейства методов run* мы и ловим все пользовательские события, потому что после старта приложения изменения в нем, скорее всего, произойдут от асинхронных взаимодействий. Перед выполнением коллбека NgZone запоминает, что сейчас в приложении могут происходить изменения, и считает вложенность коллбеков. После выполнения коллбека зона вызывает метод _checkStable прямо в рамках основного потока, не планируя это на следующую итерацию event loop.


Запускаем change detection после того, как закончится выполнение скриптов в стеке и закончат выполнение все запланированные на тот момент микротаски. Второй важный элемент внутренней зоны scheduleMicrotask:


class NgZone {  // ...  void _scheduleMicrotask(Zone _, ZoneDelegate parent, Zone zone, void fn()) {    _pendingMicrotasks++; // Count scheduled microtasks    parent.scheduleMicrotask(zone, () {      try {        fn();      } finally {        _pendingMicrotasks--;        if (_pendingMicrotasks == 0) {          _checkStable(); // Check we can try to start change detection        }      }    });  }  // ...}

Эта функция отслеживает, когда закончат свою работу все микротаски. Работа похожа на run мы считаем, сколько было запланировано микротасок и сколько уже успело выполниться. Микротасок может быть запланировано сразу много, и они обязательно выполнятся до запуска следующего скрипта. Зона вызывает _checkStable в рамках последней микротаски, не планируя еще одну.


Наконец, посмотрим в тот метод, которым все заканчивается:


class NgZone {  // ...  void _checkStable() {    // Check task and microtasks are done    if (_nesting == 0 && !_hasPendingMicrotasks && !_isStable) {      try {        // ...        _onMicrotaskEmpty.add(null); // Notify change detection      } finally {        if (!_hasPendingMicrotasks) {          try {            runOutsideAngular(() {              _onTurnDone.add(null);            });          } finally {            _isStable = true; // Set view is done with changes          }        }      }    }  }  // ...}

Тут-то мы и добрались до того самого! Этот метод проверяет, есть ли еще вложенность или невыполненные микротаски. Если все завершено, он посылает событие через _onMicrotaskEmpty. Это и есть тот самый стрим, который синхронно запускает detectChanges! Дополнительно в конце проверяется, не создалось ли в момент работы change detection новых микротасок. Если все хорошо, NgZone считает вьюху стабильной и сообщает, что проход закончился.


Подытожим:


Angular старается выполнить все в NgZone. Каждый Future при комплите, каждый Stream при каждом событии и каждый Timer по истечении времени запустит run* или scheduleMicrotask, а значит и detectChanges.


Важно помнить, что это не все. Например, addEventListener на объекте Element также обязательно расскажет текущей зоне о запланированной работе, несмотря на то что это не стрим, не таймер и не фьючер. Еще один похожий пример сам по себе вызов _zone.run() точно также запустит detectChanges, ведь мы напрямую используем NgZone.


Этот процесс оптимизирован. Метод detectChanges запустится только один раз в самом конце того таска, который его триггернул, или в рамках самой последней микротаски, которая была запланирована в прошедшем скрипте. Change detection произойдет не в следующей итерации event loop, а в текущей.


Мы в проекте используем OnPush стратегию для change detection компонентов. Это позволяет нам сильно сэкономить на этой операции. Однако как бы ни был быстр холостой запуск detectChanges, события типа scroll и mouseMove могут запускать его очень часто. Я тестировал: 1000 таких вызовов в секунду могут съесть у пользователя 200мс времени. Зависит от многих условий, но есть над чем задуматься.


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


Stream и runOutsideAngular


Основной кейс runOutsideAngular относится как раз к ситуации, когда мы слушаем очень быстрый стрим, который хотим еще и фильтровать. Например, onMouseMove у объекта Element. Быстро посмотреть под капот стрима не получится, поскольку реализаций стримов в Dart уйма. Но в статье Zones написано простое и действенное правило:


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

Зона зависит от подписки. Где она создана, там и выполняется. Поэтому рекомендуется подписываться и фильтровать быстрый стрим вне зоны Angular:


// Part of AngularDart component classfinal NgZone _zone;final ChangeDetectorRef _detector;final Element _element;void onSomeLifecycleHook() {  _zone.runOutsideAngular(() {    _element.onMouseMove.where(filterEvent).listen((event) {      doWork(event);      _zone.run(_detector.markForCheck);    });  });}

Не очевидно тут вот что зачем же класть стрим вне зоны ангуляра, если он все равно фильтруется? Было бы лаконично и без этого:


// Part of AngularDart component classfinal Element _element;void onSomeLifecycleHook() {  _element.onMouseMove.where(filterEvent).listen(doWork);}

Проблема в том, что здесь мы делаем не одну подписку. Метод where при вызове возвращает стрим. И это не тот же стрим, это новый _WhereStream:


// Part of AngularDart component classfinal Element _element;void onSomeLifecycleHook() {  _element.onMouseMove // _ElementEventStreamImpl      .where(filterEvent) // _WhereStream      .listen(doWork);}

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


Контроль зоны для package:redux_epics


Мы часто используем в наших вьюшках пакет redux_epics. Под капотом он очень активно использует стримы и принуждает и нас их использовать. Бывает, что экшены, которые мы диспатчим, могут не повлиять на наше состояние. К тому же наш change detection запустится в любом случае после того, как action отработает и что-то изменит, не стоит пинать его лишний раз. Поэтому, чтобы избежать ложных срабатываний, нужно выполнять эпики вне зоны ангуляра. Как это сделать?


Раз уж все действия стрима выполняются в зоне, внутри которой мы на него подписались, стоит поискать метод listen в коде redux_epics:


class EpicMiddleware<State> extends MiddlewareClass<State> {  bool _isSubscribed = false;  // ...  @override  void call(Store<State> store, dynamic action, NextDispatcher next) {    // Init on first call    if (!_isSubscribed) {      _epics.stream          .switchMap((epic) => epic(_actions.stream, EpicStore(store)))          .listen(store.dispatch); // Forward all stream actions to dispatch      _isSubscribed = true; // Set middleware is initialized    }    next(action);    // ...  }}

Мы его найдем в методе call. Значит подписка создается в момент вызова мидлвары (в данном случае первого вызова), а это происходит при диспатче экшена.


Отсюда простой вывод первый action нужно диспатчить вне зоны ангуляра. Например, в корневом компоненте после создания стора:


// Part of AngularDart component classfinal NgZone _zone;final AppDispatcher _element;void onInit() {  _zone.runOutsideAngular(() {    // ...    _dispatcher.dispatch(const InitApp());  });}

А если диспатчить нечего, то и null сойдет:


// Part of AngularDart component classfinal NgZone _zone;final AppDispatcher _element;void onInit() {  _zone.runOutsideAngular(() {    // ...    _dispatcher.dispatch(null);  });}

После этого стримы эпиков будут выполняться вне зоны ангуляра, что избавит от части паразитных запусков change detection.


Многократный change detection для нативных событий


Вот теперь совсем забавный трюк. Допустим, у нас есть компонент родитель, в нем есть компонент дочка, в дочке есть элемент button:


<!-- parent-component --><child-component  (click)="handleClick()"></child-component> <!-- child-component --><button  type="button"  (click)="handleClick()">  Click</button>

В каждом из этих компонентов мы слушаем нативное событие click. Оно прорастет в родителя благодаря всплыванию. Подвох в том, что change detection здесь запустится дважды. Дело в том, что в шаблоне event listeners компилируются не как подписка на стрим, а как близкий к нативному addEventListener:


_el_0.addEventListener('click', eventHandler(_handleClick_0));

Так произойдет в обоих компонентах. А значит мы переносим сюда и интересную особенность addEventListener: когда пользователь нажмет на кнопку, то браузер создаст один таск, который в рамках одной итерации event loop породит столько выполнений скриптов, сколько подписок будет затронуто всплыванием события. И после каждого скрипта будут сразу выполняться все порожденные им микротаски, а вместе с ними и detectChanges.


Поэтому в Angular выгоднее будет не рассчитывать на всплытие ивента, а сделать в дочернем компоненте Output:


<!-- parent-component --><child-component  (buttonPress)="handleButtonPress()"></child-component> <!-- child-component --><button  type="button"  (click)="handleClick()">  Click</button>

Такой вариант запустит change detection единожды, поскольку Output это стрим, а даже асинхронный стрим использует микротаски, которые, как мы уже знаем, NgZone хорошо отслеживает.


Это странное поведение всплывающих событий отлично описано в статье о микротасках все того же Джейка Арчибальда.


Как пройти в библиотеку


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


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


Закончить хотелось бы на небольшом предостережении. Зоны не очень хорошо задокументированная функциональность, и периодически с их использованием связаны баги, которые просто так не пофиксить. Вот, например, issue, который мы завели по следам одного из них. Вкратце о нем расскажу.


При создании Future сохраняет текущую зону, это дает нам некоторый контроль. Но оказалось, что в Dart SDK есть как минимум два заранее созданных и закомпличеных Future с сохраненной в них root зоной:


abstract class Future<T> {  final Future<Null> _nullFuture = Future<Null>.zoneValue(null, Zone.root);  final Future<bool> _falseFuture = Future<bool>.zoneValue(false, Zone.root);  // ...}

Еще раз напомню, что любой Future обязательно должен выполнять запланированные коллбеки в микротаске. Если мы попытаемся к Future пристыковать задачу через метод then, то он, как минимум, выполнит:


  • zone.scheduleMicrotask;
  • zone.registerUnaryCallback;
  • zone.runUnary.

Мы разбирали, что коллбек гарантированно будет регистрироваться и выполняться в той зоне, в которой его передали в метод then. А вот со scheduleMicrotask все интереснее.


У Future существует оптимизация если на один Future повешено несколько коллбеков, то он постарается выполнить их все в одной микротаске:


// Callbacks doFirstWork and doSecondWork will be called in same microtaskvoid doWork(Future future) {  future.then(doFirstWork).then(doSecondWork);}

На оба коллбека из этого примера придется только один вызов scheduleMicrotask. Круто. Но бывает, что коллбеки были повешены в разных зонах:


void doWork(Future future) {  runZoned(() {    // First zone    future.then(doFirstWork);  }, zoneValues: {#isFirst: true});  runZoned(() {    // Second zone    future.then(doSecondWork);  }, zoneValues: {#isFirst: false});}

В этом случае они все еще будут выполнены в одной микротаске. Вопрос на засыпку какая зона должна запланировать этот микротаск? Первая? Вторая? Ребята из Dart выбрали, что планировать это всегда будет зона, которая записана в изначальный Future:


// Zone that is saved in [future] argument will schedule microtaskvoid doWork(Future future) {  runZoned(() {    // First zone    future.then(doFirstWork);  }, zoneValues: {#isFirst: true});  runZoned(() {    // Second zone    future.then(doSecondWork);  }, zoneValues: {#isFirst: false});}

Это значит, что, если мы запланируем выполнение коллбека для заранее созданного и закомпличенного _nullFuture, то scheduleMicrotask будет вызван не из текущей зоны, а из root зоны:


final future = Future._nullFuture;final currentZone = Zone.current;future.then(doWork);// currentZone.registerUnaryCallback(...);// _rootZone.scheduleMicrotask(...);// currentZone.runUnary(...);

Текущая зона так и не узнает, что была запланирована микротаска. Такое поведение легко может сломать рассмотренный ранее FakeAsync: он не сможет выполнить синхронно то, о чем понятия не имеет.


Можно подумать, что _nullFuture никогда наружу не вылезет, но:


final controller = StreamController<void>(sync: true);final subscription = controller.stream.listen(null);subscription.cancel(); // Returns Future._nullFuture

Не так уж и сложно его достать, причем из совершенно неожиданного места. Отсюда и баги с FakeAsync.


Нам бы пригодилась помощь в дискуссии об этом странном поведении, заходите в issue, вместе победим! К тому же там есть дополнительная информация от контрибьюторов о том, как еще зоны взаимодействуют с Future и Stream, не упустите!


У меня все. Буду рад ответить на вопросы!

Подробнее..

Перевод Обновление роадмапа AngularDart

23.10.2020 22:21:47 | Автор: admin


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

Сегодня мы объявляем о значительном обновлении пакетов AngularDart. Этот релиз стал результатом тысяч трудочасов разработчиков и несет ряд улучшений нашим пользователям. Он повысит производительность для больших приложений, упростит наиболее распространенные варианты использования и устранит некоторые шероховатости. Если вы уже используете AngularDart, мы рекомендуем обновить его.

Также мы хотим воспользоваться этим релизом, чтобы обозначить изменение приоритетов в нашем проекте. Для многих из вас это не должно стать новостью, так как мы обсуждали это в течение последних нескольких месяцев в репозитории GitHub.

Внедрение AngularDart внутри Google шло быстро, но внешняя веб-инфраструктура очень конкурентоспособна, и мы наблюдаем скромный рост популярности фреймворка среди команд, которые не входят в Google. В связи с этим мы переориентировались на проекты Google: новую Google Play Console и Google Ads. Мы хотим честно предупредить об этом, потому что стараемся быть прозрачными в отношении наших инвестиционных приоритетов для развития в ближайшие годы.

Одна из главных причин нашего решения сфокусировать AngularDart на Google apps стремительное развитие Flutter. Уже больше двух миллионов разработчиков используют его для создания красивых, быстрых, нативных приложений для мобильных, десктопов, а теперь и для веба. Разработчики просят дальнейшего развития, и мы намерены заняться этим.

Чтобы не было недопонимания: мы продолжаем развивать AngularDart. Но превращение успешного внутреннего проекта в успешный внешний проект это немалый шаг. Открытый исходный код продукта подразумевает больше, чем просто отправку кода в репозиторий GitHub и публикацию пакетов. Для успешного опыта пользователям нужны примеры и документация. Они ждут ответов на свои вопросы, исправления ошибок и развития фичей. И мы надеемся на бльшую вовлеченность пользовательского комьюнити в названных областях.

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

  1. Как можно скорее опубликовать долгосрочный стабильный релиз основных пакетов AngularDart в pub.dev. Они будут поддерживать последний стабильный Dart SDK и иметь обновленные зависимости.
  2. Продолжить обновлять исходный репозиторий последними внутренними изменениями. Сейчас они по большей части заключаются в обеспечении null-safety в базе кода.
  3. Сосредоточиться на обновлении пакетов до новой функции null-safety.


Если вы уже работаете с AngularDart, знайте, что мы будем продолжать поддерживать вас обновлениями. Но если вы только начинаете новый веб-проект на Dart, настоятельно рекомендуем посмотреть в сторону Flutter, который является нашим предложением для кросс-платформенной разработки в долгосрочной перспективе.
Для дополнительной информации о нашем продвижении по намеченному плану, смотрите исходный репозиторий и страницу пакета.

Примечание: это объявление касается только AngularDart. Angular Javascript Framework это совершенно отдельный проект.

Комментарий от команды разработки Wrike:

Многие слышали, что наша система (Wrike) является одним из самых крупных проектов в мире, написанных на Dart и AngularDart. Поэтому отношение к этому обновлению неоднозначное. Может прозвучать странно, но, несмотря ни на что, его можно назвать позитивным. Во-первых, про AngularDart не было слышно уже достаточно давно, и, хотя у нас есть прямые каналы связи с Google, мы ждали его не меньше других. Если сделать выжимку из сообщения, то вот что это будет значить для Dart-сообщества.

Появилась прозрачность в роадмапе AngularDart. Самая большая проблема веб-мира он очень конкурентен. Даже не так, ОЧЕНЬ конкурентен. Поэтому по-серьёзному выводить фреймворк на рынок, где уже есть такие гиганты как React, Vue или старший брат Angular (TS), может стоить очень дорого. И до этого момента была какая-то недосказанность: А что будет дальше?. Будет ли AngularDart пытаться завоевать мир, останется ли нишевой историей и вообще какой план? Часто новички, приходя в мир Dart, задавали вопрос: Я хочу веб приложение, что мне использовать?. Теперь ответ есть: Google делает ставку на Flutter. Учитывая его популярность (105k звёзд на GitHub!), это весьма разумный ход. Объём проделанной работы по добавлению документации, исправлению ошибок и качеству кода настолько велик, что AngularDart очень тяжело довести до такого уровня. Тем не менее, AngularDart остаётся открытым, и сообщество может вносить исправления и улучшения во фреймворк.

Значит ли это, что нужно куда-то бежать, переписывать всё на новые рельсы? Нет, потому что поддержка AngularDart останется, и, зная сколько всего уже написано (adsence, например), скорее всего, поддержка останется с нами ещё надолго. Те пользователи, у кого AngularDart работает прямо сейчас, могут не волноваться, что их продакшн превратится в тыкву.

И самое позитивное: Flutter for Web привлекает к себе дополнительное внимание и ресурсы. Посудите сами: рынок мобильных устройств если не захвачен, то весьма неплохо накалён из-за Flutter. На рынке desktop-разработки вообще застой все используют браузеры (Electron, PWA). Рынка интерфейсов для IoT устройств вообще нет. Всё идёт к тому, что Flutter, набрав силу и скорость для входа в мир Web, сделает это в скором времени. Что из этого получится трудно загадывать, но мы надеемся, что у него всё получится.

На правах рекламы: четвёртый год подряд мы проводим DartUP, ежегодную конференцию посвященную Dart/Flutter и AngularDart. В этот раз конференция пройдёт в онлайн-формате. К сожалению, знаменитого крафтового Дарт-пива в этом году не будет. Но будет всё остальное: бесплатность, крутые спикеры, неформальное общение и неформальные форматы (да, вот так). Следите за новостями на dartup.ru и в телеграм-сообществе.
Подробнее..

Перевод Подготовка экосистем Dart и Flutter к переходу на null safety

26.02.2021 18:20:16 | Автор: admin

Поезд null safety мчится вперёд, уже почти официально анонсирован Flutter 2.0 (подключайтесь к предстоящему Flutter Engage), экосистема Dart тоже не стоит на месте. Мы перевели на русский язык новость из официального блога Dartlang и настоятельно рекомендуем вам переводить свои пакеты на новые рельсы, если вы этого ещё не сделали!

Вышел стабильный API для null safety

На днях вышла новая бета версия Dart, которая отличается повышенной стабильностью и наличием надежной null safety системы, над которой мы работали больше года. Обновленная бета (2.12.0259.9.beta) доступна на dart.dev и на бета канале Flutter. До выхода стабильной версии null-safe Dart критических изменений больше не предвидится.

Мы призываем разработчиков публиковать null-safe версии своих пакетов, чтобы пользователи смогли получить полноценную функциональность экосистемы на момент публикации стабильной версии null-safe Dart. Сами мы этот процесс уже запустили опубликовали стабильные версии null-safe пакетов, таких как args, yaml и grpc. Если все ваши зависимости в null-safe состоянии и опубликованы под стабильной версией (например, 1.0.0 вместо 1.0.0-nullsafety.123), вам пора заняться тем же!

На pub.dev мы также добавили новую фичу, которая сама размечает версии пакетов, помечая preview-релизы, если стабильная версия зависимого Dart SDK еще не вышла. Preview-релизы будут автоматически помечены как стабильные версии, как только состоится релиз стабильного Dart SDK.

Cтабильная (1.6.0) и preview (2.0.0) версии пакета args на pub.devCтабильная (1.6.0) и preview (2.0.0) версии пакета args на pub.dev

В руководстве по переходу на null safety есть вся последняя информация о том, как организовать миграцию ваших пакетов. Обратите особое внимание на ограничения Dart SDK и версии ваших зависимостей в pubspec. В том числе обратите внимание и на версию SDK, который вы используете для тестирования непрерывной интеграции (CI). Стабильная null-safe версия Dart выйдет уже скоро! Спасибо вам за поддержку!

Подробнее..

Wrike уходит от использования языка Dart. Часть 1

16.04.2021 16:20:16 | Автор: admin

Данной статьёй мы хотим пролить свет на технический стек Wrike: каким он был раньше и каким мы видим его в будущем. Мы расскажем о том, почему пять лет назад мы выбрали язык Dart основным для frontend-разработки нашего продукта и почему сейчас мы решили посмотреть в сторону других языков и фреймворков.

Что такое Wrike?

Для полноценного понимания наших технических решений необходимо рассказать, что такое Wrike как продукт. Wrike это большая SaaS платформа для управления проектами и совместной работы команд. Когда мы говорим большая, имеется в виду не только количество возможностей самого продукта (о которых вы можете почитать здесь), но и кодовая база. За годы своего развития, пока продукт рос и эволюционировал, мы прошли большой путь от:

Wrike, каким он был в 2014Wrike, каким он был в 2014

До:

Wrike 2021Wrike 2021

Столь же стремительно эволюционировали технический стек и команда разработки.

Если постараться рассказать на пальцах, что такое Wrike, то стоит отметить, что в мире управления проектами есть довольно много must have фич, без которых трудно себе представить полноценный продукт на этом рынке:

Gantt Chart, календари, таблицы и это далеко не полный набор возможностей WrikeGantt Chart, календари, таблицы и это далеко не полный набор возможностей Wrike

Это хорошо иллюстрирует сложность и комплексность UI Wrike, что накладывает дополнительную ответственность на инженерную команду с точки зрения требований перформанса, скорости разработки и стоимости поддержки.

Краткая история технического стека

Wrike появился в 2006, но так далеко мы копать не будем. Историю frontend-разработки нового времени Райка можно условно поделить на несколько этапов, рассматривая последние шесть лет.

JS + EXT

На тот момент (2013-2014) мы уже написали достаточно внушительный объём кода на чистом JS, которому тогда не было альтернатив. В качестве основного движка (или фреймворка, если хотите) мы использовали Ext.js третьей версии. Несмотря на теперешнюю архаичность, вы будете удивлены, но он по-прежнему жив-здоров! На тот момент в нём было достаточно много прорывных возможностей, которые потом, через года, трансформировались в то, к чему мы привыкли сейчас. Например, data stores с некоторой натяжкой можно считать провозвестником привычных нам stores в React.

Однако уже тогда было понятно, что JavaScript не отвечает нашим требованиям. Дело в том, что растущая команда и растущая кодовая база вынуждали нас искать язык, который предлагал бы:

  • строгую типизацию

  • большие возможности из коробки

  • хорошую работу с большими объемами кода (сборка, минимизация и т.д.)

DART. Почему не TypeScript?

2014-2015 года были сложными с точки зрения принятия инженерных решений. Мы оказались перед выбором: использовать TypeScript, который тогда только-только вышел на стабильную версию или взять Dart, который был более зрелым, но менее распространенным. Подробнее вы можете прочесть тут.

Ключевыми моментами в нашем выборе стали:

  • Более строгая типизация. Как показало время, и Dart, и TypeScript двинулись в сторону более строгой системы типов. Dart полностью перешёл на sound систему типов, TypeScript по-прежнему имеет с этим некоторые сложности.

  • Возможности из коробки. Порой third-party libraries могут быть очень полезны, а порой вредны. Одна из проблем современного мира web, и ее TypeScript не решает, это обилие библиотек, которые могут помочь ускорить разработку, но которые при этом нужно выбрать, поддерживать и время от времени обновлять. Шутки про node_modules уже вошли в историю. Dart при этом имеет достаточно богатую встроенную библиотеку, core библиотеки обновляются и поддерживаются самим Google

  • Агрессивный Tree-Shaking. Так как Wrike имеет огромный набор фичей, которые в итоге превращаются в большой объём кода, язык должен был помогать нам не загружать большое количество кода на клиент (см. Minification is not enough, you need tree shaking by Seth Ladd, a также github).

Эти и некоторые другие особенности убедили нас сделать выбор в пользу Dart. И, оглядываясь назад на почти шестилетнюю историю Dart и Wrike, мы видим, что выбор был правильным. Конечно, мы прошли долгий путь от Dart 1.x с его динамической типизацией и интеграцией с Polymer до AngularDart и Dart 2.x. Dart помогал нам год от года растить продукт с инженерной и бизнесовой точки зрения, продвигая компанию и продукт в лидеры рынка Work Management Platforms (Gartner and Forrester ratings).

Текущее состояние

Сейчас мы написали на Dart уже 2.5 миллиона строк кода, а кодовая база состоит из 365 репозиториев. Мы создали большое количество решений для сборки и проверки Dart-кода: например, Dart Code Metrics. Без преувеличения отметим, что Wrike один из самых больших потребителей Dart за пределами Google, что касается его web-ипостаси (появление Flutter способствовало взрывному росту популярности Dart, но пока ещё по большей мере в мире мобильной разработки).

Однако реальность такова, что язык сам по себе не может дать все необходимые инструменты для построения большого web-приложения. Экосистема имеет не менее, а, может, и более важное значение. Системы сборки, подсветки синтаксиса, интернационализации, фреймворки для View без этого невозможно себе представить современную разработку.

Экосистема Dart

Мы бы не хотели полностью пересказывать документацию, поэтому сосредоточимся на наиболее важной части фреймворках. Несмотря на то, что теоретически Dart позволяет работать со всеми web-фреймворками через JS interop, на самом деле выбор не очень большой:

  • OverReact обёртка над React от Workiva.

  • Flutter for Web популярный кроссплатформенный фреймворк, написанный на Dart, с недавнего времени поддержка web вышла в стабильной версии.

  • AngularDart де-факто стандарт для разработки web-приложений на Dart.

Другие решения возможны, но неудобны либо трудно реализуемы. Таким образом, выбирая Dart для web-разработки, вы вынуждены взять один из этих трёх фреймворков либо писать что-то своё.

Главные причины нашего ухода от разработки на Dart

В предыдущей части мы дали краткий обзор на текущее состояние экосистемы языка Dart, касаясь в основном её frontend-части. Пришло время подойти к одной из ключевых проблем, которая на момент написания этой статьи, к сожалению, не решена: при всём желании невозможно выбрать техническое решение на века. Мир frontend-разработки постоянно движется вперёд, развиваясь, порой, даже быстрее, чем того хотелось бы. Веб-браузеры также развиваются, добавляя новые и новые возможности и расширяя API. Веб-фреймворки, которые, казалось бы, по определению созданы для того, чтобы абстрагироваться от нижележащих слоёв, тоже вынуждены реагировать на это.

Вдобавок к этому существуют и модные течения даже в весьма хаотичном мире фронтенда. Какое-то время назад это был прогрессивный рендеринг (React Fiber, Angular Ivy). Сейчас появляется тенденция в виде отказа от глобальных state managers, для примера можно рассмотреть Effector. GraphQL, Server Side Rendering можно найти достаточно много вещей, которые обязательно должны быть поддержаны в современном веб-фреймворке.

Становится очевидно, что для SaaS-продукта, который движется вперёд со скоростью сверхзвукового истребителя, жизненно важно, чтобы техническая основа, фундамент, из которого он состоит, развивалась так же быстро.

И в этом фундаменте есть два составляющих элемента:

  • Код, который ваши инженеры пишут.

  • Код, который ваши инженеры НЕ пишут.

Современная разработка (особенно на фронтенде) щедро сдобрена использованием third-party библиотек и инструментов. Да что там, сейчас можно запустить продукт на рынок, вообще не написав ни строчки кода (так называемый no-code подход)! Тем не менее, код, который вы не написали это, с одной стороны, время, которое вы сэкономили, а с другой риск, который вы берёте на себя.

Разработка крупного продукта это всегда сложный баланс между написанием собственных решений / переиспользованием готовых / взаимодействием с разработчиками сторонних фреймворков. И используемые язык и фреймворк как одни из самых обширных и всепроникающих частей разработки становятся её наиболее уязвимым местом. В былые годы, когда продукты распространялись на дисках и концепция Continuous Delivery ещё не появилась, смена языка или фреймворка могла стоить критически дорого. Сейчас же, особенно с появлением концепции micro frontends, это не только не должно быть трагедией, а, наоборот, служит здоровым механизмом эволюционного развития.

Со всем вышесказанным приходится признать, что мы пришли к точке, где нам приходится пересмотреть свой текущий технический стек как не отвечающий нашим требованиям. Несмотря на то, что язык Dart и его экосистема движутся вперёд (в том числе благодаря взрывному росту популярности Flutter), а язык Dart становится всё лучше и лучше (например, с null safety) один ингредиент всё равно отсутствует web-фреймворк. Да, в самом языке уже есть примитивы, которые позволяют работать с DOM напрямую, но такая разработка может подойти для индивидуальных разработчиков, а не для больших команд.

Под отсутствием web-фреймворка мы имеем в виду, что никакое из существующих решений для языка Dart не обладает четырьмя необходимыми для современного web-фреймворка качествами:

  • Feature richness. Обеспечение работы со всеми (или большинством) возможностей, которые предоставляет современный web.

  • Performance.

  • Поддержка сообщества.

  • Развитие и добавление новых возможностей.

Если более пристально посмотреть на существующие фреймворки для языка Dart, то мы увидим, что:

AngularDart

Де-факто стандарт для веб-приложений. Отвечал почти всем требованиям, но, к сожалению, Google-команда сдвинула приоритет его развития в сторону Flutter. Это следует не только из твиттера Tim Sneath (менеджер Dart & Flutter):

Переписка о судьбе AngularDartПереписка о судьбе AngularDart

Но и из более официальных источников. Также можно прочесть ветку на GitHub. Да, AngularDart по-прежнему на месте, он жив, его можно использовать. Но ему не хватает одного из ключевых элементов: Развитие и добавление новых возможностей.

OverReact

Портированная версия React для Dart. К сожалению, поддержка комьюнити не очень большая, а сам проект разрабатывается в основном компанией Workiva.

Flutter for Web

Именно на этот фреймворк делают ставку инженеры Google. И он действительно движется вперёд взрывными темпами! Но то, что подойдёт небольшим компаниям или при портировании мобильного приложения на web, к сожалению, не подходит для наших задач.

В данный момент для нас есть несколько блокеров, связанных с Flutter Web. Основной это то, что невозможно встроить Flutter-приложение внутрь текущего веб-приложения. Ведь, к сожалению, Flutter нельзя обернуть в веб-компонент. Это очень сильно мешает концепции микро-фронтендов, основная идея которой состоит в том, что всю функциональность мы делим на независимые приложения. Эти приложения деплоятся и разрабатываются разными командами и имеют слабую связанность друг с другом. Если вы хотите узнать больше, на это есть соответствующий баг. Выходом было бы заворачивать микро-фронтенды в iframe, но это сопряжено с рядом трудностей технического характера.

Помимо этого, Flutter пока не имеет ряда немаловажных для современного web возможностей, например SSR или SEO.

Немаловажный аспект связан и со скоростью приложения: пока не совсем ясно, насколько Flutter сможет справиться, допустим, с табличным представлением. Мы проведём это исследование, когда появится возможность встраивать Flutter-приложение внутрь другого.

Таким образом, несмотря на нашу любовь к Dart и годы, которые мы прошли вместе, мы приняли решение двигаться в сторону изменения нашего технического стека, так как основная задача компании обеспечить возможность разработки приложения и через 2 года, и дальше, а с AngularDart мы объективно не можем этого гарантировать.

Важно отметить, что мы не прощаемся: Dart по-прежнему будет большой частью нашей разработки. Но для новых проектов мы теперь будем рассматривать другие возможности. Какие?

И, для сохранения интриги, точно по канонам всех сериалов, именно здесь мы поставим многоточие. Каков же будет наш новый стек, из чего и как мы выбирали, вы узнаете из второй части этой статьи. Подписывайтесь на наши социальные сети и следите за новостями!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru