訳者 はじめに
Connie Ooiさんによる「Announcing Dart 3.13」(原文)の日本語訳です。勝手に訳して、後から許可をとる。
Dart 3.13の発表
Dart のコードベース、ツール、プラットフォームに簡潔さとシンプルさをもたらします。

Dart 3.13のリリースを発表できることを大変嬉しく思います!
今回のリリースでは、すべての開発者が好むテーマ、「クリーンで軽量なコード」に焦点を当てています。Dart 3.13では、このテーマを開発者体験のあらゆる層に反映させています。言語面では、プライマリコンストラクタによって繰り返しの多い定型コードが排除され、1行のコードでクラスを定義できるようになりました。開発ツール面では、dart formatの改良により、 Effective Dart スタイル規則に従ってコードを整理し、dart pubにはモノリポジトリを整理するための新しいワークスペースコマンドが導入されました。pub.dev では、<callout-box> タグや {@example} ディレクティブといった充実したドキュメント機能により、パッケージのドキュメントがより読みやすくなりました。内部的には、ネイティブライブラリのツリーシェイク (@RecordUse) や WebAssembly の遅延読み込みにより、コンパイルされたアプリケーションバイナリを可能な限りコンパクトに保つことができます。
さあ、Dartをアップデートするか、flutter upgradeを実行して、Dart 3.13の新機能を一緒に見ていきましょう!
言語の更新
プライマリコンストラクタ
Dart 3.12での実験的なプレビューを経て、プライマリコンストラクタがDart 3.13で正式に安定版となりました!
プライマリコンストラクタは、クラスの簡潔性において大きな前進です。クラス本体やパラメータリストでフィールド名や型を繰り返し記述するのは煩わしいという開発者からの声を多く受けました。プライマリコンストラクタはこの繰り返しを排除し、例えば2つのフィールドとコンストラクタを持つPointクラスを、たった1行のコードで定義できるようになります:
class Point(final int x, final int y);
この機能には、new または factory キーワードを使用した コンストラクタの簡潔な構文 も含まれており、空の宣言本体を ; で終了できるようになりました。
プライマリコンストラクタの採用を促進し、コードベース全体で一貫したスタイルを維持できるよう、自動修正機能を備えた新しいリンターをいくつか導入しました:
empty_container_bodies: クラス風の宣言において、{}の代わりに;を使用することを推奨し、簡潔さを促します。initialize_in_field_declaration: フィールドの初期化をプライマリコンストラクタからフィールド宣言へ移動させることで、明瞭さを促進します。unnecessary_const_in_enum_constructor: 列挙型のコンストラクタからconstを削除することで、簡潔さを推奨します。unnecessary_primary_constructor_body: 不要なプライマリコンストラクタの本体を削除することで、簡潔さを促進します。unnecessary_type_name_in_constructor: コンストラクタ宣言において、型名をnewに置き換えることで、簡潔さを推奨します。use_declaring_parameters: 可能な場合は宣言型パラメータを使用することで、コードの簡潔化を推奨します。
これらのリンティングに加え、コードの移行を自動化するための新しいIDEリファクタリングも追加しました:
- プライマリコンストラクタへの変換:条件を満たすインボディコンストラクタをプライマリコンストラクタに変換します。
- インボディコンストラクタへの変換:プライマリコンストラクタをインボディコンストラクタに変換します。
- 宣言パラメータへの変換: プライマリコンストラクタ内の非宣言パラメータを宣言パラメータに変換します。
- 初期化をフィールド宣言へ移動: コンストラクタの初期化リストからフィールド宣言へフィールドの初期化を移動します。
詳細はこちら: プライマリコンストラクタのドキュメント
Webプラットフォームの更新
WebAssembly の遅延読み込みプレビュー
私たちは、Web 上の Dart における WebAssembly の機能拡張に精力的に取り組んできました。Dart 3.13 では、dart2wasm でコンパイルする際の遅延読み込みサポートの初期プレビューを公開できることを嬉しく思います。実験的なコンパイラフラグを使用して、今すぐ有効にすることができます:
dart compile wasm -O2 --enable-deferred-loading
注
遅延読み込みを利用するには、アプリを埋め込む側が、Wasmモジュールのバイトデータを読み込むためのコールバックを提供する必要があります。詳細については、アプリで生成された <app>.mjs ファイル内の CompiledApp.instantiate のドキュメントを参照してください。
遅延読み込みを使用する大規模なアプリケーションにおいて、dart2wasm は dart2js と比較して初期ページ読み込み(IPL)時間を大幅に短縮し、Flutter Web アプリと DOM ベースの Web アプリケーションの両方にメリットをもたらします。
なお、dart:html や package:js などのレガシーWebライブラリは dart2wasm ではサポートされておらず、Dart 3.7で非推奨となりました(将来的には dart2js でも動作しなくなります)。多くのパッケージはすでに移行済みであるため、パッケージの依存関係をアップグレードするだけで問題が自動的に解決されることがよくあります。ただし、コードでこれらのレガシーライブラリを直接使用している場合は、移行ガイドを参照して、サポートされている dart:js_interop および package:web ライブラリを今すぐ採用してください。
詳細はこちら: WebAssembly ドキュメント
コアエンジンおよびランタイムの更新
エンジニアリングのハイライト
Webプラットフォームの進捗と並行して、私たちのチームはDartの基盤を絶えず改善しています。ここ3ヶ月間に裏方で取り組んできた作業のハイライトをいくつかご紹介します:
- 型昇格の安全性に関する修正:ネストされた関数に関連する型昇格におけるまれな安全性上の問題を修正しました。
- 共通フロントエンドのリファクタリング:Dartアナライザーとのコード共有をさらに促進するため、内部の共通フロントエンドコンポーネントをリファクタリングしました。
- DDCモジュールシステムの統一:単一の統一されたモジュールシステムを確立するため、各ツールでDart Development Compiler(DDC)のレガシーなモジュールシステムを削除する作業を引き続き進めました。
- ネイティブランタイムのメモリ安全性: Dart ヒープの周囲にメモリケージを導入することで、ネイティブランタイムのメモリ安全性を強化しました。
- 動的モジュールリンク: [動的モジュール](https://github.com/dart-lang/sdk/blob/
f29989d506b2dd82957da5d1917bcc76628c5ddb/pkg/dynamic_modules/README.md) に関する実験を開始し、動的なコードリンクを可能にすることで、チーム内でのプロトタイプの迅速な共有など、開発ワークフローの改善に向けた道筋を築きました。
ツールの更新
Dart フォーマッタ
コードベースを整理し、読みやすく保つことは、dart format の最優先事項です。Dart 3.13 では、フォーマットされたコードをさらに見やすくするために、いくつかのスタイルの微調整を導入しました。変更のほとんどは軽微なものですが、いくつかの重要な更新により、日常的に扱うコードの可読性が著しく向上します:
- メソッド呼び出しのフォーマット不備の修正: 最適化が誤って発動し、コードのフォーマットが崩れてしまうバグを修正しました。この問題は主に、大規模なコレクションリテラルを含むメソッド呼び出しの周辺で発生していました:
// 変更前:
await MethodChannelContainer()
.onMethodChannelInvoke('reportCrash', <String, Object?>{
'time': nowTime,
'errorValue': errorName,
'reason': reason,
'stacktrace': stacktrace,
});
// 変更後:
await MethodChannelContainer().onMethodChannelInvoke(
'reportCrash',
<String, Object?>{
'time': nowTime,
'errorValue': errorName,
'reason': reason,
'stacktrace': stacktrace,
},
);
// 変更前:
await MethodChannelContainer()
.onMethodChannelInvoke('reportCrash', <String, Object?>{
'time': nowTime,
'errorValue': errorName,
'reason': reason,
'stacktrace': stacktrace,
});
// 変更後:
await MethodChannelContainer().onMethodChannelInvoke(
'reportCrash',
<String, Object?>{
'time': nowTime,
'errorValue': errorName,
'reason': reason,
'stacktrace': stacktrace,
},
);
- メソッドチェーンの分割に関するヒューリスティック: フォーマッタがメソッド呼び出しチェーンを
.の位置で分割するか、引数リストの内部で分割するかを決定するヒューリスティックを変更しました。メソッドチェーンのターゲットが、要素を1つだけ持つコレクションリテラル、または引数を1つだけ持つ関数呼び出しである場合、ターゲットそのものではなく、呼び出しチェーンを分割することを優先するようになりました:
// 以前:ターゲットを分割:
function(
argument,
).method().another();
// 変更後:チェーンを分割:
function(argument)
.method()
.another();
// 以前:ターゲットを分割:
function(
argument,
).method().another();
// 変更後:チェーンを分割:
function(argument)
.method()
.another();
- インポートセクションの区切り: フォーマッタは、一連のインポート内の異なる「セクション」の間に空白行を挿入するようになりました。フォーマッタはインポートを並べ替えませんが、『Effective Dart』のルールに従って、それらを区切るようになりました:
// 以前: import 'dart:io'; import 'dart:math'; import 'package:args/args.dart'; import 'package:test/test.dart'; import 'my_library.dart'; // 変更後: import 'dart:io'; import 'dart:math'; import 'package:args/args.dart'; import 'package:test/test.dart'; import 'my_library.dart';
// 以前:
import 'dart:io';
import 'dart:math';
import 'package:args/args.dart';
import 'package:test/test.dart';
import 'my_library.dart';
// 変更後:
import 'dart:io';
import 'dart:math';
import 'package:args/args.dart';
import 'package:test/test.dart';
import 'my_library.dart';
dart_styleの変更履歴には他にもいくつかのスタイルの微調整が記載されていますが、これらが最も目立つ変更点です。書式変更は煩わしい作業を引き起こす可能性があることを承知しているため、スタイルの変更には慎重に取り組んでいます。一方で、スタイルの改善はコードの可読性を高めることにつながり、生成AIのおかげでこれまで以上に多くのコードをレビューするようになった現代において、これは大きなメリットとなります。最適化に関するバグ修正を除き、これらのスタイル変更は 言語バージョンに依存 しています。コードを Dart 3.13 にアップグレードして初めて、これらの変更が反映されます。
詳細はこちら: Dart フォーマッタのドキュメント
Pubの更新
パッケージドキュメントの更新
明確で構造化されたパッケージドキュメントは、健全なエコシステムにとって不可欠です。パッケージ作成者がより整理され、メンテナンスしやすいドキュメントを作成できるよう支援するため、pub.dev では <callout-box> タグと {@example} ディレクティブを全面的にサポートするようになりました。これにより、不要な定型コードを非表示にしたまま、サンプルファイルからコードスニペットを直接 API ドキュメントに抽出できます。
たとえば、example/foo.dart 内でスニペット領域を次のように定義します:
// example/foo.dart
void main() {
// #region abc
// ドキュメントに含める部分
foo();
assert(false); // #hide
// #endregion
}
// example/foo.dart
void main() {
// #region abc
// ドキュメントに含める部分
foo();
assert(false); // #hide
// #endregion
}
次に、Dartのドキュメントコメント内でそのリージョンを参照します:
/// これは素晴らしい関数です。
///
/// 使用例:
/// {@example /example/foo.dart#abc}
void foo() {}
/// これは素晴らしい関数です。
///
/// 使用例:
/// {@example /example/foo.dart#abc}
void foo() {}
pub.devは、生成されたドキュメント内で指定されたコードリージョンを直接表示します。

pub.dev上のcupertino_uiドキュメント。埋め込まれた{@example}コードブロックの横に、青い枠線付きの<callout-box>が表示されています。
また、pub.devでは裏側で、dartdocファイルの検索用に2階層のハッシュインデックスを導入しました。この更新により、生成ファイルが10万以上ある大規模なパッケージのドキュメントレンダリング速度が劇的に向上しました。

新しいブロブインデックスの導入後のドキュメントリクエストのレイテンシの低下。
詳細はこちら: CupertinoActivityIndicator クラスのドキュメント
ネイティブ相互運用性の更新
@RecordUse および package:record_use によるネイティブライブラリのツリーシェイク
効率性へのこだわりは、コンパイルされたアプリケーションバイナリに至るまで徹底されています。dart:ffi や Code Assets を使用して、ネイティブの C、C++、または Rust コードと相互運用する Dart および Flutter アプリをビルドする場合、Dart コンパイラは従来から未使用の Dart ラッパー関数をツリーシェイクしてきました。しかし、基盤となるネイティブバイナリは従来、そのまますべてバンドルされたままでした。
@RecordUse(package:meta内)とpackage:record_useにより、DartおよびFlutterでは、Dartコードとともにネイティブライブラリもツリーシェイクできるようになりました。これにより、最終的なアプリケーションバンドルには、アプリが実際に呼び出すネイティブコードのみが含まれるようになります。
仕組み
- ネイティブ FFI バインディングに
@RecordUse()を付与する: ネイティブ FFI バインディング(ffigenによって生成されたものなど)に@RecordUse()アノテーションを付与します:
import 'dart:ffi'; import 'package:meta/meta.dart'; @RecordUse() @Native<Int32 Function(Int32, Int32)>() external int sqlite3_open( Pointer<Utf8> filename, Pointer<Pointer<sqlite3>> ppDb, ); @RecordUse() @Native<Int32 Function(Pointer<sqlite3>)>() external int sqlite3_close(Pointer<sqlite3> db);
import 'dart:ffi';
import 'package:meta/meta.dart';
@RecordUse()
@Native<Int32 Function(Int32, Int32)>()
external int sqlite3_open(
Pointer<Utf8> filename,
Pointer<Pointer<sqlite3>> ppDb,
);
@RecordUse()
@Native<Int32 Function(Pointer<sqlite3>)>()
external int sqlite3_close(Pointer<sqlite3> db);
-
コンパイル中の到達可能な呼び出しの追跡: Dart AOT ビルドにおけるプログラム全体のコンパイルおよびツリーシェーキングの過程で、コンパイラは
@RecordUse()でアノテーションされたバインディングのうち、実行可能コード内で到達可能なものを追跡します。ツリーシェーキングによって削除される未使用の Dart コード内にある呼び出しは、自動的に除外されます。 -
リンクフックで未使用のネイティブシンボルを削除: パッケージのリンクフック(
hook/link.dart)内で、input.recordedUsesを取得します。package:record_useを使用して記録された呼び出しを照会し、ネイティブツールチェーン(package:native_toolchain_cやRustのビルドスクリプトなど)に対し、Dartが実際に呼び出すシンボルのみを保持するよう指示します:
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:record_use/record_use.dart';
import 'package:my_package/src/c_library.dart';
import 'package:my_package/src/record_use_mapping.dart';
void main(List<String> arguments) async {
await link(arguments, (input, output) async {
// Extract symbols for functions called in reachable Dart code:
final symbolsToKeep = input.recordedUses?.calls.keys
.cast<Method>()
.map((method) => recordUseMapping[method.name]!);
await cLibrary.link(
input: input,
output: output,
linkerOptions: LinkerOptions.treeshake(
symbolsToKeep: symbolsToKeep,
),
);
});
}
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:record_use/record_use.dart';
import 'package:my_package/src/c_library.dart';
import 'package:my_package/src/record_use_mapping.dart';
void main(List<String> arguments) async {
await link(arguments, (input, output) async {
// Extract symbols for functions called in reachable Dart code:
final symbolsToKeep = input.recordedUses?.calls.keys
.cast<Method>()
.map((method) => recordUseMapping[method.name]!);
await cLibrary.link(
input: input,
output: output,
linkerOptions: LinkerOptions.treeshake(
symbolsToKeep: symbolsToKeep,
),
);
});
}
これが重要な理由
- バイナリサイズの劇的な削減:SQLite、暗号化、画像デコーダ、オーディオエンジンなどの重いネイティブ依存関係は、数百もの関数を公開しています。リリースビルドの際、リンカーは参照されていないネイティブコード、デッドオブジェクトファイル、および未使用のエクスポートを削除します。
- 未使用のネイティブライブラリの完全な除外: アプリケーションがパッケージのネイティブバインディングを一度も呼び出さない場合、リンクフックによってネイティブバイナリが最終的なアプリケーションバンドルから完全に削除されます。
- ツールとのシームレスな統合:
ffigenなどのツールは生成されたバインディングに自動的にアノテーションを付与し、package:native_toolchain_cはリンクフックと直接連携して、エンドツーエンドのネイティブツリーシェーキングを実現します。
詳細はこちら: ネイティブリンクフックのドキュメント および Record useのドキュメント
まとめ
Dart 3.13 は、よりシンプルでクリーンな開発体験という私たちのビジョンを具現化します。プライマリコンストラクタのような表現力豊かな言語機能を、よりスマートなツール、充実した pub.dev のドキュメント、そしてコンパクトなネイティブ・ツリーシェーキングと結びつけることで、このリリースは、皆さんが最も重要なこと、つまり素晴らしいアプリの構築に集中できるよう支援します。
このリリースのすべては、皆様からの実運用での知見、バグ報告、パッケージへの貢献に基づいています。Dartエコシステムが繁栄しているのは、皆様の積極的な参加のおかげであり、この言語の今後の方向性を形作る上で、皆様と協力できていることに深く感謝しています。
皆様がDart 3.13を使ってどのような作品を生み出すのか、今から楽しみにしています!ぜひ今日、少し時間を割いて Dart を更新 するか、flutter upgrade を実行し、GitHub イシュートラッカー での議論に参加してください 。情熱あふれるコミュニティの皆様、私たちと共にDartの未来を築いてくださり、ありがとうございます!
詳細はこちら: Dart SDK 変更履歴