Dart 3.14 — что нового в Dart?
Вместе с Flutter 3.50 вышел Dart 3.14. После насыщенных 3.12 и 3.13, где появились приватные именованные параметры и primary constructors, этот релиз посвящён не языку, а инструментам и платформам. Главная новинка — команда dart migrate, которая сама поднимает версию SDK в пакетах. Кроме того, сильно расширен dart:js_interop, ускорен Wasm, а dart:mirrors объявлен устаревшим.
dart —version # если Dart установлен отдельно flutter upgrade # если вы работаете с Flutter
Коротко о главном
|
Направление |
Что изменилось |
|---|---|
|
Миграции |
Новая команда dart migrate пошагово поднимает SDK-констрейнт и применяет фиксы |
|
CLI |
dart run -e для однострочников, быстрее dart run/dart test с экспериментами, dart pub upgrade pkg@… |
|
Web / interop |
Много новых конвертеров в dart:js_interop, тип JSError, флаг —no-deprecated-js-interop |
|
Wasm |
try_table по умолчанию, обязательные wasm:js-string builtins, SIMD через v128 |
|
Библиотеки |
Большое расширение Int32x4, rangeEquals для typed data, arm64e в dart:ffi |
|
Deprecations |
dart:mirrors помечен @deprecated |
|
Breaking |
Убраны случайно попавшие в 3.13 API Isolate.*Sync, изменились toString() и min/max у SIMD-типов |
|
IDE |
Analysis server быстрее стартует и быстрее ищет ссылки |
Примечание: в changelog SDK для 3.14 нет раздела Language. Новых языковых фич в этом релизе нет, и обновление ничего не меняет в синтаксисе вашего кода.
dart migrate: обновление SDK одной командой
Раньше переход на новую версию Dart выглядел так: вручную поднять sdk: в pubspec.yaml, прогнать dart fix, разобраться с тем, что сломалось, и повторить для каждого пакета монорепозитория. В 3.14 этим занимается dart migrate.
Как работает команда:
-
поднимает нижнюю границу SDK-констрейнта по одной минорной версии за шаг: 3.12 → 3.13 → 3.14;
-
на каждом шаге сначала применяет подготовительные фиксы (prepare), затем поднимает констрейнт (bump);
-
необязательные фиксы очистки (cleanup), которые переводят код на новые возможности языка, запускаются только по явному запросу;
-
по умолчанию обрабатывает текущий каталог, но можно передать список каталогов пакетов;
-
зависимые друг от друга пакеты мигрируют синхронно, поэтому констрейнты остаются разрешимыми на каждом шаге. Если какая-то зависимость не поддерживает новую версию, пакет пропускается;
-
пакет уже должен требовать Dart 3.0 или новее.
Основные флаги:
# Посмотреть, что будет изменено dart migrate —dry-run # Применить изменения dart migrate —apply # Перепрыгнуть сразу через несколько версий dart migrate —apply —target-sdk=3.14 # Дополнительно перевести код на новые языковые фичи dart migrate —apply —step=cleanup # или —step=all
Шаги можно запускать и по отдельности: —step=prepare, —step=bump, —step=cleanup или —step=all.
Пример: пакет на Dart 3.12, который мы хотим перевести на 3.14.
|
ДО |
ПОСЛЕ dart migrate —apply —target-sdk=3.14 |
|---|---|
|
sdk: ^3.12.0 |
sdk: ^3.14.0 |
|
код, который сломался бы на 3.13/3.14 |
исправлен подготовительными фиксами |
|
старый синтаксис конструкторов |
остаётся как есть, пока не запущен —step=cleanup |
Последняя строка важна. cleanup может, например, перевести классы на primary constructors из Dart 3.13. Это стилистическое изменение и часто большой дифф, поэтому по умолчанию оно не выполняется.
Примечание: dart migrate не заменяет dart fix. dart fix применяет точечные исправления в рамках текущей версии языка, а dart migrate переводит пакет на следующую версию SDK. Список флагов можно посмотреть через dart migrate —help.
Подробнее: dart migrate, issue #63240.
Другие улучшения CLI
dart run -e: Dart как однострочник
dart run научился выполнять код прямо из командной строки, без файла-скрипта. Импорты пакетов берутся из конфигурации текущего пакета, а если её нет, зависимости можно подтянуть на лету через -P / —package-constraint:
dart run -e ‘print(DateTime.now().toUtc());’ dart run -P http:^1.0.0 -e ‘ import «package:http/http.dart» as http; void main() async { final r = await http.get(Uri.parse(«https://dart.dev»)); print(r.statusCode); }’
Есть и флаг —offline. Подробнее в issue #64298.
Быстрее dart run и dart test
-
Скомпилированный исполняемый файл для dart run <package>:<command> и dart test теперь кэшируется в .dart_tool/dartdev/bin/ (раньше в .dart_tool/pub/bin/) и пересобирается при изменении любого исходника. Раньше для path-зависимостей снапшот собирался на каждом запуске, а для hosted-пакетов с path dependency_override мог оказаться устаревшим.
-
Platform.script в таком запуске указывает на Dart-исходник, а не на kernel-снапшот, поэтому относительные пути работают так же, как при запуске из исходников.
-
—enable-experiment=… больше не заставляет запускать код из исходников: для экспериментов собирается свой снапшот, и старт перестаёт замедляться.
dart pub upgrade для конкретных пакетов
dart pub upgrade http@^1.4.0 # обновить в рамках констрейнта dart pub upgrade http@latest # до последней версии dart pub upgrade http@resolvable # выяснить, что мешает взять версию новее
Подробнее: dart-lang/pub#4569.
Analysis server
Сервер анализа стал заметно отзывчивее: быстрее работают поиск ссылок (Find References), старт, перезапуск после правки analysis_options.yaml и плагины анализатора. Сильнее всего это заметно в больших монорепозиториях. Фоновые задачи теперь реже блокируют форматирование и другие запросы редактора.
Formatter
-
dart format учитывает эксперименты из окружающего analysis_options.yaml.
-
Исправлено падение при форматировании enum с primary constructor, когда сохраняются висячие запятые.
-
Исправлена пропущенная висячая запятая у значений такого enum. Это изменение не зависит от версии языка, поэтому затронет и код на Dart 3.13.
-
Исправлено падение на строке ровно со 128 пробелами отступа. Кто-то это действительно нашёл.
Анализатор и линтер
-
Новое правило future_sync_value советует использовать Future.syncValue вместо Future.sync, если колбэк возвращает синхронное значение (#61740).
-
Анализатор выдаёт ошибку js_interop_extension_constructor_js_annotation_has_no_effect, если @JS повесили на external-конструктор или фабрику extension type из dart:js_interop. Аннотацию нужно перенести на само объявление extension type.
// ДО: линтер предложит заменить final f = Future.sync(() => 42); // ПОСЛЕ final f = Future.syncValue(42);
Web и JS interop
Это самая объёмная часть релиза. Цель понятна: убрать причины держаться за старые dart:html и package:js и открыть дорогу к Wasm.
Аудит устаревшего interop
В dart2js (dart compile js) и в DDC появился флаг —[no-]deprecated-js-interop. С —no-deprecated-js-interop импорт устаревших библиотек становится ошибкой:
dart:html, dart:html_common, dart:indexed_db, dart:js, dart:js_util, dart:svg, dart:web_audio, dart:web_gl.
Кроме того, соответствующие условия dart.library.* в условных импортах и в bool.fromEnvironment становятся false. Во Flutter этот же флаг доступен в flutter build web, flutter run и flutter test —platform chrome.
dart compile js —no-deprecated-js-interop -o out.js web/main.dart
Подробнее: issue #63919.
Новое в dart:js_interop
Конвертация массивов примитивов. Раньше приходилось писать .map(…).toList() с ручным приведением каждого элемента. Теперь есть готовые расширения для num, String и bool, включая nullable-варианты:
import ‘dart:js_interop’; final JSArray<JSNumber> jsNums = [1, 2, 3].toJS; final List<int> ints = jsNums.toDartIntList; final List<double> doubles = jsNums.toDartDoubleList; final JSArray<JSString> jsTags = [‘dart’, ‘wasm’].toJS; final List<String> tags = jsTags.toDartStringList;
Промисы и FutureOr.
-
Статические методы JSPromise.resolve и JSPromise.reject.
-
JSArray.fromAsync создаёт массив из асинхронного генератора или из списка асинхронных значений.
-
Расширения toJSPromiseOrValue и toDartFutureOr конвертируют значения, которые могут быть как синхронными, так и асинхронными, без лишнего оборачивания в промис.
-
Function.toJS и Function.toJSCaptureThis поддерживают функции, которые возвращают Future<T>, где T — это void или подтип JSAny?. Возвращаемый Future автоматически превращается в JSPromise (#63496).
-
При конвертации Future в JSPromise JS-ошибки (JSAny) теперь передаются в rejected-промис как есть, в JS Error заворачиваются только Dart-ошибки (#61353).
@JS() external set loadUser(JSFunction f); void main() { // Раньше пришлось бы вручную вызывать .toJS у Future внутри функции loadUser = ((JSString id) async { final user = await fetchUser(id.toDart); return user.name.toJS; }).toJS; }
Прочее:
-
новый тип JSError для JS-класса Error;
-
геттеры JSFunction.length и JSFunction.name;
-
toJSVarArgs и toJSCaptureThisVarArgs собирают аргументы JS-функции в массив, а не раскладывают их по отдельным параметрам;
-
external-члены расширений можно объявлять на type-параметрах, ограниченных JS interop-типом, например extension <T extends JSObject> on T (#61248);
-
isA<JSArray> проверяет одновременно Array.isArray и instanceof, и значение считается массивом, если сработало хотя бы одно условие (#62699).
dart2wasm
-
По умолчанию используются стандартизированные инструкции обработки исключений WebAssembly try_table (#54394).
-
Теперь обязательна нативная поддержка builtins wasm:js-string. Заодно добавлены обходы багов WebAssembly.compileStreaming в Safari 26.5 и более ранних версиях (#63543). Вместе с изменениями во Flutter 3.50 это и делает возможным Wasm в Safari.
-
Int32x4, Float32x4, Float64x2 и их списки компилируются в аппаратные 128-битные SIMD-инструкции WebAssembly (v128) (#64170).
-
dart.library.isolate при компиляции в Wasm равно false в условных импортах (#64328).
-
dart compile wasm —standalone больше не поддерживает dart:js_interop.
Библиотеки
dart:typed_data: SIMD без костылей
Int32x4 долго был «бедным родственником» Float32x4. В 3.14 он получил почти полный набор операций:
-
конструкторы Int32x4.splat и Int32x4.zero();
-
операторы ~, унарный -, сдвиги <<, >>, >>>;
-
abs, andNot, min, max;
-
сравнения по полосам (lane-wise): equal, notEqual, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual. Каждое возвращает -1 в полосах, где условие выполнено, и 0 в остальных;
-
геттеры anyTrue и allTrue.
Кроме того, у всех целочисленных typed-списков и у ByteData появился метод rangeEquals для быстрого сравнения диапазонов (#64095).
import ‘dart:typed_data’; final a = Int32x4(1, -2, 3, -4); final mask = a.lessThan(Int32x4.zero()); // [0, -1, 0, -1] print(mask.anyTrue); // true final b = a.abs(); // полосы: 1, 2, 3, 4 print(b.x); // 1 print(b); // ‘V128’ — см. breaking changes ниже final header = Uint8List.fromList([0x89, 0x50, 0x4E, 0x47]); // rangeEquals позволяет сравнить, например, сигнатуру файла // с началом буфера без копирования подсписков.
dart:ffi
-
NativeFinalizer.callback возвращает колбэк, с которым был создан финализатор (#63811).
-
Abi.iosArm64e и Abi.macosArm64e для архитектуры arm64e с pointer authentication (#63420).
dart:io
На macOS запуск процессов переведён с fork + exec на posix_spawn. Из-за этого PATH теперь разрешается по окружению родительского процесса, а не дочернего. Рекомендация простая: передавайте в Process.start и соседние методы абсолютный путь к исполняемому файлу.
dart:mirrors объявлен устаревшим
Библиотека помечена @deprecated и будет удалена в одном из будущих релизов (#44489). Во Flutter и в вебе она давно недоступна, так что это касается в основном серверного и CLI-кода. Если в CI стоит dart analyze —fatal-infos, после обновления он упадёт на deprecated_member_use. Самое время переходить на кодогенерацию.
Breaking changes
-
dart:isolate: из публичного API убраны экспериментальные Isolate.runSync, Isolate.create, Isolate.shutdownSync, Isolate.pinToCurrentThread, Isolate.isPinnedToCurrentThread, Isolate.runEventLoopSync, Isolate.onEvent и Isolate.handleEvent. В Dart 3.13 они попали в релиз случайно. NativeCallable.isolateGroupBound теперь доступен только с VM-флагом —experimental-shared-data (#64285).
-
dart:typed_data: toString() у Float32x4, Int32x4 и Float64x2 теперь возвращает ‘V128’, а не список значений полос. Прежний вывод отличался на разных платформах и нигде не был специфицирован (#63847).
-
dart:typed_data: min/max у Float32x4 и Float64x2 следуют IEEE 754 на всех платформах: если в полосе NaN, результатом будет NaN, а -0.0 считается меньше +0.0 (#63962).
-
dart:io (macOS): изменилось разрешение PATH при запуске процессов (см. выше).
Экосистема и документация
Между релизами 3.13 и 3.14 в блоге Dart вышли:
-
Bringing Primary Constructors to Dart (Bob Nystrom) — история самой ожидаемой фичи 3.13;
-
Skills CLI 1.0 (Jake Macdonald) — как поставлять skills для AI-агентов вместе с пакетами;
-
Announcing Genkit Dart 1.0 (Chris Gill) — фреймворк для агентных приложений на Dart и Flutter;
-
Google Summer of Code 2026 results (Jonas Finnemann Jensen).
На dart.dev появились руководства по dart migrate, по skills для AI-агентов (package:skills, package:skills_lint, Developer Knowledge MCP server), по data-driven фиксам, по компиляции и деплою Wasm, примеры миграции на package:web, установка Dart на Windows через winget, а также описание нескольких include в analysis_options.yaml. У страницы changelog появились фильтры, поиск и постоянные ссылки. Также обновлён DartPad.
Подробнее: What’s new на dart.dev.
Заключение
Dart 3.14 — спокойный инфраструктурный релиз, и в этом его ценность. Вместо новых языковых конструкций команда занялась тем, чтобы обновление стало рутинной операцией (dart migrate), а переход веба на Wasm перестал упираться в старый interop. Если вы уже на 3.13, обновление почти ничего не сломает. Исключения — экзотические SIMD-сценарии, случайно опубликованные API изолятов и dart:mirrors.
Что попробовать:
-
dart migrate —dry-run на одном пакете;
-
dart compile js —no-deprecated-js-interop или flutter build web —no-deprecated-js-interop;
-
dart run -e вместо очередного scratch.dart.
Теги:
- dart migrate
- dart js interop
- flutter
- wasm
- 3.14
Хабы:
- Dart
- Flutter
- WebAssembly
- Разработка мобильных приложений
- Программирование