Dart 3.13 更新,一起看看新特性

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);

这里的参数同时承担了:

  1. 构造函数参数
  2. 实例字段声明

因此不再需要把同一份信息重复写两次。

几个基本规则:

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 真正值得关注的部分。

相关推荐
GGBond今天继续上班1 小时前
给 DeepSeek Harness 写了个生图插件,补上了原生对话生图能力
人工智能·github·deepseek
苏灿烤鱼2 小时前
十个 CLI 坐进一间办公室,协调层靠得住吗?
typescript·github·agent
冷雨夜中漫步2 小时前
DeepSeek Harness:一切皆插件的 AI Agent 框架深度解析
java·人工智能·ai·开源·github
马丁玩编程3 小时前
GitHub 3.5k Star 之后,Ragent AI 框架新版本来了
后端·面试·github
xiezhr4 小时前
DeepSeek Harness 值得安装的 15 款插件
github·agent·deepseek
峰向AI17 小时前
24000本书,一个项目全搞定:这个开源宝藏让我重新认识了读书
github
mCell17 小时前
GitHub Actions 玩法大赏:一台远程主机的七种活法
linux·github·agent
hajimi18 小时前
Agent Skill 实战:我把大疆行业无人机上云的坑,固化成了一个技能包
github