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

Dartlang

Зоны в 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, не упустите!


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

Подробнее..

Перевод Релиз Dart 2.10 на шаг ближе к null-safety

09.10.2020 20:15:45 | Автор: admin

image<img src="http://personeltest.ru/aways/habrastorage.org/webt/bx/6z/zo/bx6zzobmc1fbzqkkzocurdk7gsg.jpeg" />


Команда языка Dart постепенно приближается к одному из самых мажорных релизов null-safety. Эта фича есть у многих лидеров рынка, включая Kotlin и TypeScript. По понятным причинам этот релиз нельзя сделать в виде рубильника: одним днём и простым апдейтом. Набравшее скорость сообщество, выпустившее огромное количество пакетов уже не сможет перейти на мажорную версию по щелчку пальцев. Поэтому процесс этот поступательный и не такой быстрый, как хотелось бы. Тем не менее сомневаться в том, что уже довольно скоро язык станет значительно лучше и удобнее не приходится!


Мы в Wrike не смогли обойти стороной обновление релиз Dart 2.10 и переводим статью из официального блога Dartlang.


Новый, унифицированный dart для всех ключевых задач. А также апдейт по таймлайну null-safety и принципах миграции.


Авторы: Майкл Томсен и Кевин Мур


Сегодня мы анонсируем новый Dart, версия 2.10 (two-dot-ten). В этом релизе представлен новый унифицированный Dart: единый инструмент для всех возможных задач разработчика, таких как создание проектов, анализ и форматирование кода, тестирование и компиляция приложений. У нас есть обновленная информация об этапах работ по null-safety, а также принципы миграции существующего кода.


Новый унифицированный инструмент разработчика Dart


Dart составляет основу Flutter SDK: он не только предоставляет язык и среду выполнения, которые используются в приложениях Flutter, но и поддерживает многие основные задачи разработчика, такие как форматирование, анализ и тестирование кода. Однако, в то время как Flutter всегда имел один универсальный инструмент разработчика (команда flutter), у Dart исторически было много небольших инструментов (например, dartfmt и dartanalyzer). Dart 2.10 обзавелся новым, унифицированным инструментом разработчика dart, очень похожим на инструмент flutter. Этот новый инструмент поддерживает все основные задачи, такие как создание новых проектов и пакетов, анализ и форматирование кода, а также запуск, компиляцию и тестирование проектов. Чтобы использовать этот инструмент, просто выполните команду dart:



Flutter включает новый Dart во Flutter SDK. Начиная с версии Flutter 1.22 SDK, директория <flutter-sdk>/bin (которая чаще всего находится в PATH) содержит команды и flutter, и dart. Если вы занимаетесь разработкой как на Flutter, так и на Dart, вы получаете возможности обоих из одного пакета Flutter SDK без необходимости устанавливать что-либо еще.


Если хотите загрузить и установить другой Dart SDK (если вам требуется другая версия), убедитесь, что SDK инструмента dart, который вы хотите использовать по умолчанию, находится в начале переменной окружения PATH.

В следующих стабильных релизах мы планируем расширить функциональность инструмента dart и постепенно отказаться от меньших инструментов (dartdoc, dartfmt, dartanalyzer и т.п.). В 2021 году мы планируем выпустить Dart SDK, содержащий исключительно инструмент dart. Рекомендуем вам переключиться на новый инструмент при запуске команд Dart сейчас, будь то вручную в терминале или в системах сборки (CI), и сообщить нам, если чего-то не хватает или что-то работает не так, как должно.


О null-safety


Мы хорошо поработали с null-safety с тех пор, как несколько месяцев назад запустили первую техническую превью. В полной null-safety мы видим инструмент предотвращения трудно обнаруживаемых null-ошибок, а также дополнительный бонус к улучшению производительности. Если хотите узнать больше, рекомендуем нашу новую страницу Understanding null safety. Если предпочитаете короткое видео, посмотрите the null safety video с мероприятия Flutter Day, прошедшего несколько месяцев назад.


Когда null-safety будет готова к использованию? Вот наш прогноз:


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


  2. Ранняя миграция пакетов с помощью бета-версии: позже в этом году мы завершим улучшение производительности и обеспечим достаточное количество тестов, чтобы быть уверенными, что функция работает должным образом и что обратная совместимость надежна. К тому времени мы опубликуем бета-версию этой функции, и вам не нужно будет передавать экспериментальный флаг. Надеемся, что владельцы пакетов начнут их миграцию в null-safety, тем самым проведя последний раунд проверки, насколько эта функция готова к стабильному выпуску.


  3. Использование в продакшене stable: в зависимости от обратной связи на запуск бета-версии мы исправим все оставшиеся проблемы, а затем опубликуем их в stable. Трудно назвать конкретные сроки, но мы думаем о начале следующего года. Как только эта функция станет стабильной, мы надеемся на широкое внедрение null safety приложениями, опубликованными в сторах, и многими и пакетами, опубликованными в pub.dev в стабильных версиях.



Принципы перехода к null-safety


Мы хотели бы поделиться нашими главными принципами для миграции на null-safety.


Переходите, когда будете готовы


Null-safety это фундаментальное изменение в системе Dart. Это меняет основы объявления переменных, потому что мы решили сделать переменные ненулевыми по умолчанию:



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


Переходите постепенно, по порядку


Мы настоятельно рекомендуем переносить код по порядку, начиная с листьев графа зависимостей. Например, если C зависит от B, который зависит от A, сначала перенесите в null-safety A, затем B, затем C. Этот порядок применяется независимо от того, являются ли A, B и C библиотеками, пакетами или приложениями.


Почему порядок так важен? Вы можете добиться некоторого успеха в переносе кода до миграции ваших зависимостей, но вы рискуете столкнуться с необходимостью повторной миграции, если ваши зависимости изменят свои API в процессе переноса. Мы предоставим инструменты, которые помогут вам узнать, какие из зависимостей были успешно перенесены. Если вы являетесь автором пакета, то, чтобы избежать риска нарушения API, подождите, пока все ваши зависимости не будут перенесены, прежде чем публиковать null-safe версию.


Используйте автоматизированные инструменты для снижения затрат на миграцию


Когда ваши зависимости готовы и вы решаете мигрировать, можете использовать наш инструмент миграции. Он работает, анализируя весь ваш существующий код, ищет, какие объявления могут быть не не допускающими null (они остаются без изменений), а какие должны допускать значение null (нужен маркер ? null в объявлении).


Инструмент миграции интерактивен, поэтому вы можете просмотреть свойства, которые он определил. Если вы не согласны с каким-либо из выводов инструмента, можно добавить подсказки о допустимости пустых значений, чтобы изменить вывод. Например, если вы хотите сделать API не допускающим null, даже если потребуется некоторый рефакторинг, можно сообщить об этом инструменту и повторно запустить анализ миграции. Добавление всего нескольких подсказок по миграции может оказать огромное влияние на качество перехода.



Получите полную выгоду при полном использовании


Как только весь ваш код и пакеты, от которых он зависит будет перенесен, он может выполняться с гарантированной null-safety. До этого момента ваш код будет работать и компилироваться так же, как сейчас, но абсолютная null-safety обеспечит полную верификацию времени выполнения и оптимизацию компилятора. Выполнение тестов с полной null-safety поможет избежать проблем с присваиванием null во время выполнения, а компиляция приложений с null-safety гарантирует, что вы получите оптимизации сразу и в будущем, например, меньший по размеру скомпилированный вывод и более быстрое выполнение.


Дальнейшие действия


Новый инструмент разработчика dart доступен сегодня в SDK Dart 2.10 и в SDK Flutter 1.22. Если у вас уже есть Flutter SDK, вы можете получить Dart SDK с помощью команды dart, просто запустив flutter upgrade; это даст вам Flutter 1.22 SDK, в который встроен Dart 2.10. Рекомендуем вам немедленно переключиться на новый инструмент dart и связаться с нами, если что-то отсутствует или не работает должным образом.


Скоро у нас будет больше новостей о null-safety. Скорее всего, в течение следующего месяца, когда наши друзья в команде Flutter будут иметь фреймворк Flutter с поддержкой null-safety, готовый к экспериментам. Следите за обновлениями в блоге Flutter. А пока вы можете поэкспериментировать с null-safe кодом Dart с помощью DartPad с null-safety и узнать больше о дизайне функции в документации по null-safety.

Подробнее..

Перевод Обновление роадмапа 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 и в телеграм-сообществе.
Подробнее..

Перевод Объявление о бета-тестировании null-safety Dart. Начало процесса миграции пакетов в надежное и безопасное состояние

27.11.2020 20:16:23 | Автор: admin


Безусловно, null-safety важный шаг в развитии языка. Команда Dart анонсировала бета-релиз версии с null-safety! Мы перевели на русский новость об этом релизе, в котором вы узнаете, как мигрировать на новые версии, какие преимущества получите, и в чем польза null-safety для всех нас.

Сегодня мы объявляем, что бета-версия с надежной null-safety доступна для Dart и Flutter. Null-safety это наше последнее важное достижение, призванное помочь вам избежать ошибок обращения к null-значениям класса ошибок, которые часто трудно обнаружить. Это видео в общих чертах объясняет причину нашей радости:


С переходом на бета-версию с null-safety пришло время для миграции тысяч пакетов, доступных на pub.dev. Мы перенесли библиотеки ядра Dart, фреймворк Flutter и более 40 пакетов Dart и Flutter. При этом мы надеемся, что комьюнити примет null-safety, мигрируя свои пакеты.



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

Выбор в пользу null-safety


Прежде чем обсуждать миграцию на null-safety, хотим повторить, что (как указано в наших принципах null-safety) у вас есть возможность выбирать, когда именно начинать переход. Приложения и пакеты будут работать только с null-safety, если их минимальное ограничение Dart SDK принадлежит по крайней мере к пред-релизной версии Dart 2.12:

environment: sdk: ">=2.12.0-0 <3.0.0"

Чтобы испытать ее, попробуйте создать небольшое null-safety приложение hello (например, с помощью dart create), содержащее код, как показано ниже. Затем можете попробовать запустить приложение до и после изменения ограничения SDK и запуска dart pub get и посмотреть, как меняется поведение программы. (Убедитесь, что dart --version возвращает вам именно 2.12)

bin/hello.dart:...void main() {  var hello = 'Hello Dart developers';  if (someCondition) {hello = null;  }  print(hello);} Before changing the SDK constraint:$ dart run null After changing the SDK constraint (and running dart pub get):$ dart run bin/hello.dart:6:13: Error: Null can't be assigned to a variable of type 'String' because 'String' is not nullable. hello = null;        ^


Переход к null-safety


Чтобы мигрировать пакет (или простое приложение) в режим null-safety, выполните следующие пять шагов, которые подробно описаны в руководстве по миграции на dart.dev.

Шаг 1: проверьте, готовы ли ваши зависимости


Настоятельно рекомендуем переносить код по порядку, начиная с листьев графа зависимостей. Например, если C зависит от B, который зависит от A, сначала мигрируйте на null-safety A, затем B, затем C. Этот порядок применим вне зависимости от того, являются A, B и C библиотеками, пакетами или приложениями.



Почему порядок так важен? Можно добиться некоторого прогресса в миграции кода до миграции зависимостей, но есть риск столкнуться с необходимостью повторного прогона, если ваши зависимости изменят свои интерфейсы во время миграции. Если некоторые из ваших зависимостей не null-safety, подумайте о том, чтобы связаться с издателями пакетов, используя контактные данные, указанные для каждого пакета на pub.dev.

Проверка готовности зависимостей


Чтобы проверить, готово ли ваше приложение или пакет к началу миграции, можете выполнить dart pub outdated в режиме null-safety. Приведенный ниже пример показывает, что приложение будет готово к миграции, если обновит свои зависимости на path, process и pedantic до пред-релизных версий, перечисленных в столбце Resolvable.



Если поддержка null-safety доступна в новых минорных версиях, вы увидите их в столбце Upgradable. Часто поддержка null-safety будет доступна в мажорных новых версиях; в этом случае вы увидите версии, перечисленные в разделе Resolvable в выводе утилиты outdated. Для перехода на них отредактируйте файл pubspec.yaml, чтобы разрешить эти мажорные версии. Например, можете поменять
process: ^3.0.13 на process: ^4.0.0-nullsafety.

Также вы можете найти пакеты с поддержкой null-safety на pub.dev, используя новые теги Null safety на страницах пакетов (например, collection 1.15) и новую опцию Расширенного поиска null safety search.



Шаг 2: перенос с помощью инструмента миграции


Если зависимости готовы, можете приступать к переносу вашего приложения или пакета с помощью инструмента миграции dart migrate.

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



Несколько авторов пакетов Dart протестировали миграцию с использованием ранних предварительных сборок null-safety, и их отзывы были воодушевляющими. В руководстве по миграции есть дополнительные подсказки по использованию инструмента миграции.

Шаг 3: статический анализ перенесенного кода


Обновите пакеты с помощью pub get в вашей IDE или в командной строке. Затем используйте IDE или командную строку для выполнения статического анализа вашего кода Dart:

$ dart pub get$ dart analyze


Или на коде Flutter:

$ flutter pub get$ flutter analyze


Шаг 4: убедитесь, что тесты проходят


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

Шаг 5: опубликуйте свой null-safety пакет


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

  • Обновите версию до следующей мажорной версии (например, с 2.3.x до 3.0.0). Это гарантирует, что пользователи вашего пакета не обновятся до него, пока не будут готовы использовать null-safety. Это дает вам свободу рефакторинга API, чтобы наилучшим образом применить null-safety.
  • Переведите и опубликуйте свой пакет в качестве предварительной версии на pub.dev. (Например, используйте 3.0.0-nullsafety.0, а не 3.0.0.)


Для более подробной информации о миграции и управлении версиями см. руководство по миграции.

Преимущества гарантированной null-safety


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

Более безопасный код


Совсем недавно мы обнаружили ошибку в основной ветке Flutter, из-за которой различные команды инструмента flutter вылетали на определенных конфигурациях машины с ошибкой null: The method '>=' was called on null. Основной проблемой был недавний пулл-реквест на добавление поддержки для обнаружения Android Studio 4.1. Этот пулл-реквест добавил такой код:

final int major = version?.major;final int minor = version?.minor;if (globals.platform.isMacOS) {  /// plugin path of Android Studio changed after version 4.1.  if (major >= 4 && minor >= 1) {    ...


Можете найти ошибку? Поскольку версия может быть null, как основная, так и дополнительная версия также могут быть null. Этот баг может показаться легко находимым здесь в изоляции, но на практике подобный код проскальзывает постоянно, даже при строгом процессе код-ревью, который используется в репозитории Flutter. При null-safety статический анализ сразу же отлавливает эту проблему:



Это была довольно простая ошибка. На ранних этапах использования null-safety во внутреннем коде Google мы видели, как благодаря null-safety обнаруживаются, а затем решаются гораздо более сложные ошибки. Вот несколько примеров:

  • Внутренняя группа обнаружила, что они часто проверяют наличие null значений в коде, которые null-safety определяла как никогда не null. Эта проблема чаще всего встречается в коде, использующем protobuf, где необязательные поля возвращают значение по умолчанию, когда оно не задано, и никогда не имеют значения null. Это приводило к тому, что код неправильно проверял условие по умолчанию, смешивая значения по-умолчанию и null значения.
  • Команда Google Pay обнаружила баги в своем коде Flutter, из-за которых они не могли получить доступ к объектам Flutter State вне контекста Widget. До null-safety они возвращали null и маскировали ошибку; при null-safety анализ определил, что эти свойства никогда не могут быть null, и выдал ошибку анализа.
  • Команда Flutter обнаружила ошибку, из-за которой движок Flutter потенциально мог выйти из строя, если null был передан параметру scene в Window.render(). Во время миграции на null-safety они добавили подсказку, чтобы пометить Scene как не допускающую нулевое значение, а затем смогли легко предотвратить потенциальные сбои приложения, которые мог вызвать переданный null.


Использование надежной null-safety во время компиляции


Надежность null-safety Dart имеет еще одно важное значение: компиляторы Dart могут использовать информацию о допустимости значений null. Это может сделать ваши программы меньше и быстрее. Пока что у нас не так много реальных приложений, полностью переведенных на null-safety (ведь мы только сейчас начинаем миграцию экосистемы пакетов, от которых эти приложения зависят в плане надежности), но мы видим очень обнадеживающие результаты из основного фреймворка.

Недавно мы провели тестовую перекомпиляцию образца hello_world, чтобы измерить влияние null-safety на размер приложения. Это минимальный пример, который просто отображает hello world. При сравнении общего размера скомпилированного кода размер несжатого (установленного на устройстве) кода сократился на 3,5% без каких-либо действий кроме перекомпиляции с надежной null-safety. Это стало возможным несмотря на то, что все приложение состояло из 10 строк кода, потому что размер кода всех включенных библиотек сократился; например, сам фреймворк Flutter (package:flutter) сократился на 3,9%.

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

В некоторых случаях мы уже видели, как null-safety приводила к росту производительности, когда переход обнаруживал изъян в логике кода. Например, мы обнаружили проблему в кеше позиционирования текста во Flutter web. Этот кеш использовал ключ, допускающий значение null, а затем по заданной логике использовал TextAlign.start при null. Эта логика вызывала ошибку в кеше, когда элементы выглядели так, будто они изменились, даже если у них все еще было значение по умолчанию. В результате часто случались нерезультативные обращения в кеш. Добавление геттера textAlign, не допускающего значения null, помогло исправить ошибку кеширования, что привело к увеличению производительности рендеринга текста в 14 раз в случаях кешируемого текста.

Начните сегодня!


Бета-версии Dart и Flutter, содержащие null-safety, уже готовы. Если вы пишете на Flutter, можете переключиться на бета-версию с помощью flutter channel beta, а затем flutter upgrade. А если вы не используете Flutter, то можете получить автономный Dart SDK из архива Dart SDK.

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

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

Спасибо за вашу поддержку и обратную связь! Мы работаем над тем, чтобы сделать Dart более надежным языком, а Flutter более мощным фреймворком.

Michael Thomsen, продакт-менеджер Dart и Flutter, опубликовал эту статью в официальном блоге Dartlang. Если вы хотите послушать доклад Майкла и пообщаться с ним лично, приходите на DartUP 2020 Online 4 и 5 декабря и обсудите последние обновления языка с командой Dart и сообществом.
Подробнее..

Перевод Подготовка экосистем 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