Dart 3.13 看起来只是一个普通的小版本,但这次更新覆盖的范围其实相当广。
如果只看最显眼的变化,可能会觉得它只是加入了更简洁的构造函数语法。但真正值得关注的是另外几条线:
- Primary Constructors:继续降低 Dart 的样板代码量
- Native Tree Shaking:让 Dart 的全程序优化第一次真正跨过 FFI 边界
- Wasm Deferred Loading:开始解决大型 Flutter Web 首屏加载问题
- Dynamic Modules:Dart 开始探索运行时动态加载代码
- dartdoc / pub.dev / formatter:持续改善整个开发工具链
把这些变化放在一起看,会发现 Dart 3.13 的方向很明确:
Dart 不再只是优化「Dart 代码本身」,而是在继续向 Native、Web、构建系统和运行时边界扩张。
1. Primary Constructors:Dart 的数据类终于可以更简洁
以前定义一个简单的数据类,我们通常需要同时声明字段和构造函数:
kotlin
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
Dart 3.13 的 Primary Constructors 可以把它压缩成:
arduino
class Point(final int x, final int y);
这里的参数同时承担了:
- 构造函数参数
- 实例字段声明
因此不再需要把同一份信息重复写两次。
几个基本规则:
arduino
final int x
表示不可变字段。
csharp
var x
表示可变字段。
而:
arduino
int x
只是构造参数,并不会自动生成字段。
Constructor 本身也变短了
Dart 3.13 还允许在类内部使用 new 来声明普通 constructor,从而避免重复写类名。
概念上可以理解为:
scss
Point(...) → new(...)
Point.origin(...) → new origin(...)
factory Point.clone(...) → factory clone(...)
这看起来只是语法糖,但意义其实比较明确:
Dart 正在主动减少 class 中无意义的重复信息。
而且官方同时加入了 lint 和 IDE Refactor,希望开发者能够逐步迁移到新的写法。
2. Native Tree Shaking:这可能是 Dart 3.13 最重要的变化
相比 Primary Constructors,我认为 Native Tree Shaking 对 Flutter 生态的长期影响更大。
过去 Dart 的 Tree Shaking 有一个明显边界:
Dart 编译器只能很好地优化 Dart 世界,却很难把优化继续传递到 FFI 后面的 Native 世界。
假设一个 Flutter Package 内部带了一套 C/C++/Rust Library。
这套 Library 一共有:
300 个 Native API
而 App 实际上只调用:
20 个 API
过去 Dart AOT 可以知道哪些 Dart Wrapper 没有被使用,于是把它们删除。
但是底下的 Native Library 仍然可能完整进入 APK、IPA 或桌面应用。
问题在于:
diff
Dart Compiler
↓
知道哪些 Dart FFI Binding 被用了
↓
---------------- FFI Boundary ----------------
↓
Native Linker
↓
不知道 Dart 到底调用了哪些东西
也就是说,代码可达性信息在 FFI 边界丢失了。
3. Dart 3.13 开始打通 Dart Compiler 和 Native Linker
Dart 3.13 引入了一套新的机制:
css
@RecordUse
package:record_use
Link Hook
核心思路并不复杂。
首先 Dart AOT Compiler 分析整个程序:
markdown
Whole Program
↓
Reachability Analysis
↓
哪些 FFI Binding 实际可达?
然后把这些信息记录下来。
接着通过:
LinkInput.recordedUses
传递给 Package 的 Link Hook。
Link Hook 再把:
javascript
Dart FFI Binding
↓
Native Symbol
建立对应关系。
最终 Native Linker 就可以知道:
哪些 Native Symbol 应该保留,哪些实际上根本不会被 Dart 调用。
整个流程变成:
markdown
Dart Source
↓
Dart AOT
↓
Reachability Analysis
↓
recordedUses
↓
Link Hook
↓
Native Symbols
↓
Native Linker
↓
Tree Shaking
如果一个 Native Library 完全没有被使用,理论上甚至可以不进入最终 Bundle。
4. 为什么 Native Tree Shaking 很重要?
这件事不能只理解成:
「Flutter App 又可以小几 MB 了。」
它更大的意义在于 Code Assets。
Code Assets 想解决的问题是:
Dart Package 如何自然地携带、编译、链接 C/C++/Rust 代码?
以前 Dart 和 Native 基本还是两条相对独立的流水线:
css
Dart Code ─────→ Dart Compiler
C / C++ / Rust ─→ Native Toolchain
现在则开始逐渐变成:
markdown
┌→ Dart AOT
Dart Package │ ↓
│ Reachability
│ ↓
└→ Native Build
↓
Link Hook
↓
Native Linker
换句话说:
Native Code 开始真正参与 Dart 的 Whole-Program Optimization。
长期来看,这会降低 Flutter Package 引入 Rust、C、C++ 依赖的成本。
Package 作者可以提供一套很完整的 Native API:
Package 提供 300 个 API
但应用只为真正使用的那部分承担最终二进制体积。
这对 SQLite、Codec、Crypto、AI Runtime 等大量依赖 Native Library 的 Package 都很有价值。
5. Web:Wasm 终于开始支持真正的 Deferred Loading
大型 Flutter Web App 长期存在一个明显问题:
Initial Bundle 太大。
以前 Dart 代码即使使用 deferred,到了 Wasm 世界里,也不一定真的能把代码拆成独立模块按需加载。
Dart 3.13 开始为 dart2wasm 提供 Deferred Loading:
css
--enable-deferred-loading
这样 Deferred Dart Code 可以真正拆成独立的 Wasm Module:
sql
App Start
↓
Main Wasm Module
↓
用户访问某个功能
↓
Load Deferred Wasm Module
这对于大型 Flutter Web App 非常重要,因为理论上可以减少首次需要下载和编译的 Wasm 代码。
不过目前它仍然属于比较早期的实验阶段。
Embedder 需要自己负责提供加载 Wasm Module Bytes 的 callback,因此暂时还没有达到 Webpack/Vite Code Splitting 那种完全透明的体验。
但方向已经比较明确:
css
Flutter Web
↓
Wasm
↓
Code Splitting
↓
Lazy Loading
这条链正在逐渐补齐。
6. Runtime:Dart 开始探索 Dynamic Modules
Runtime 部分还有一个很值得关注的实验:
Dynamic Modules
Dart AOT 长期依赖一个非常重要的前提:
Closed-World Assumption
简单来说就是:
编译时
=
整个程序已经确定
正因为编译器知道「世界里一共有哪些代码」,它才能进行非常激进的:
Tree Shaking
Inlining
Reachability Analysis
AOT Optimization
但这同时意味着:
运行过程中动态加入一段新的 Dart Code 很困难。
Dynamic Modules 正在尝试打破这个限制。
概念上变成:
sql
Main AOT App
↓
Runtime
↓
Dynamic Dart Module
↓
Link / Load
目前官方探索的主要目标仍然是 开发工作流,尤其是在无法正常使用 JIT 的移动平台环境下改善 prototype 和开发体验,而不是直接提供生产环境 Code Push。
所以现在还不能简单理解成:
ini
Dynamic Modules = Flutter Code Push
但它值得长期关注。
因为一旦 Dart Runtime 真正建立成熟的 Dynamic Module Model,很多过去受 closed-world assumption 限制的能力都有可能出现新的实现空间。
7. Runtime 还加入了 Memory Cage
Dart 3.13 还开始在 Dart Heap 外部加入 Memory Cage。
它的目标主要是加强 Native Runtime 的内存安全。
这件事没有 Primary Constructors 那么容易直接被普通 Flutter 开发者感知,但也反映出一个趋势:
Dart Runtime
↓
不仅关注 GC / Performance
↓
也开始进一步强化 Native Memory Safety
随着 Dart 与 Native Code 的整合越来越深,这类 Runtime Security 能力的重要性也会越来越高。
8. dartdoc:文档示例终于可以和真实代码共用一份 Source of Truth
Dart 3.13 的 dartdoc 加入了一个很实用的能力:
less
{@example}
以前我们经常这样维护 API Documentation:
bash
example/foo.dart
↓
复制代码
↓
API Documentation
问题是时间久了以后:
markdown
真实 Example 已更新
↓
Documentation 没更新
↓
Example Drift
最终文档里的代码可能已经不能运行了。
现在 dartdoc 可以直接引用 example/ 中的真实代码区域。
结构变成:
bash
example/foo.dart
↓
Single Source of Truth
↓
┌───────────────┐
↓ ↓
Executable dartdoc
Example Example
还可以通过 #hide 隐藏为了让 Example 真正运行而存在、但没有必要展示给读者看的 Boilerplate。
这是一个小功能,但设计思路很好:
Documentation Example 不应该是一份复制出来的代码,而应该尽可能来自真正可以执行的代码。
9. pub.dev:开始优化超大型 dartdoc
pub.dev 也更新了文档文件索引机制。
新的两级 Hash Index 主要针对拥有大量生成文件的大型 Package。
对于达到十万级 dartdoc 文件的 Package,文档查找和渲染延迟可以明显降低。
普通 App 开发者基本感知不到这个变化。
但对于大型 Package 和 API Documentation Infrastructure 来说,这是典型的:
Scale Problem
说明 Dart 生态的基础设施也在继续处理越来越大的 Package。
10. dart format:Method Chain 和 Import 更自然了
Dart 3.13 也调整了 formatter 的一些启发式规则。
一个核心原则是:
尽量保持简单的调用目标完整,把复杂度放到调用链上。
例如以前可能出现:
php
function(
argument,
).method().another();
现在 formatter 更倾向于:
php
function(argument)
.method()
.another();
这样阅读逻辑明显更自然:
php
function(argument)
↓
.method()
↓
.another()
而不是先把一个非常简单的函数调用拆碎。
Import 也会自动按照来源分组:
arduino
import 'dart:io';
import 'dart:math';
import 'package:foo/foo.dart';
import 'package:bar/bar.dart';
import 'src/local.dart';
也就是:
makefile
dart:
↓
package:
↓
project
不同类别之间自动加入空行。
这些都不是重大语言能力,但会持续改善 Dart Codebase 的一致性和可读性。
11. 如何理解 Dart 3.13?
如果把这次更新按照影响层级分类,大概可以这样看:
| 层级 | 更新 | 意义 |
|---|---|---|
| Language | Primary Constructors | 减少 Boilerplate |
| Compiler | Native Tree Shaking | 优化跨越 FFI Boundary |
| Native | Record Use + Link Hook | Native Code 参与 Whole-Program Optimization |
| Web | Wasm Deferred Loading | 为大型 Web App 做 Code Splitting |
| Runtime | Dynamic Modules | 探索运行时动态加载 Dart Code |
| Security | Memory Cage | 提升 Native Runtime 内存安全 |
| Docs | {@example} |
Example 与 Documentation 共用 Source of Truth |
| Infrastructure | Pub Hash Index | 提升大型 Package 文档性能 |
| Tooling | Formatter | 改善 Method Chain / Import Formatting |
但其中真正值得长期关注的其实是三件事:
sql
Native Tree Shaking
Wasm Deferred Loading
Dynamic Modules
因为这三项分别在突破 Dart 原来的三个边界:
sql
Dart → Native
Dart → Web/Wasm
AOT → Runtime Dynamic Loading
12. 更大的趋势:Dart 正在扩大自己的「编译边界」
过去可以把 Dart 的 Compiler Model 简化理解为:
markdown
Dart Source
↓
Dart Compiler
↓
Dart Program
现在正在逐渐变成:
markdown
┌→ Dart AOT
│
Dart Package ───────┼→ Wasm Modules
│
└→ C / C++ / Rust
↓
Link Hook
↓
Native Binary
与此同时 Runtime 又开始探索:
sql
Static AOT Program
+
Dynamic Dart Modules
因此 Dart 3.13 最值得注意的地方可能并不是某一个 Feature。
而是 Dart 的边界正在继续扩张:
Compiler 开始理解 Native Code 的使用情况,Web Compiler 开始真正拆分 Wasm,Runtime 开始研究动态模块。
Primary Constructors 是开发者最容易立即感知的变化。
但 Native Tree Shaking、Code Assets、Wasm Deferred Loading 和 Dynamic Modules,可能才是这次更新里更值得长期观察的方向。
如果这些能力继续成熟,未来 Dart/Flutter 的形态可能不再只是:
用 Dart 写一个跨平台 App。
而会越来越接近:
以 Dart 为中心,统一组织 Dart、Wasm 与 Native Code 的跨平台编译和运行体系。
这可能才是 Dart 3.13 真正值得关注的部分。