Flutter Web 启动性能优化实践
本文字数:4307 字
预计阅读时间:30 分钟
01 前言
在之前的文章中,我们详细介绍过 Flutter Web 从0到部署上线的流程,并且针对其中遇到的问题进行了分析与修复。我们也对 Flutter Web 的项目进行了打包部署上线。在产品体验中会发现,跟传统的 web 端网页对比,还是明显感觉到性能上的差异,特别是启动的过程明显耗时较长。本文将针对 Flutter Web 项目做一些启动上的优化和探讨。SDK 版本:Flutter SDK 3.19.0。
02 main.dart.js 分包
main.dart.js
是 Flutter Web 的编译产物,所有的业务逻辑都在这一个 js 文件里,可想而知它会比较臃肿。在 Flutter Web 发布后,就有了 main.dart.js
分包的需求,使用 deferred-components
即可达成分包的目的。
1、添加依赖
通过将 deferred-components
依赖添加到应用程序的 pubspec.yaml
中的 flutter
下即可:
flutter:
deferred-components:
2、实现
我们找到一个可以被延迟加载的 Widget
,使用 deferred
关键字进行导入:
import './widget/me_read_achievement_widget.dart' deferred as achievement;
然后调用 loadLibrary()
:
late Future<void> _libraryFuture;
void initState() {
super.initState();
_libraryFuture = achievement.loadLibrary();
}
最后使用 FutureBuilder
等待 loadLibrary
的 Future
对象初始化完成,并实现延迟加载:
build(BuildContext context) {
return FutureBuilder<void>(
future: _libraryFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return achievement.MeReadAchievementWidget();
}
return const CircularProgressIndicator();
},
);
}
Widget
3、编译产物
打包完成后我们看下编译产物,会发现除了 main.dart.js
之外又多了一个 main.dart.js_1.part.js
:
main.dart.js_1.part.js
即为分包。
4、分包加载
部署之后我们打开浏览器看一下效果:
在启动 Web 页面后我们发现只加载了 main.dart.js
:
只有操作到相应的页面,才会加载 main.dart.js_1.part.js
:
这说明如果业务比较复杂页面很多的情况下,我们可以根据自己的需求和体验细化项目的结构,将 Widget 进行拆分,在需要的时候再进行延迟加载,一定程度上减少 main.dart.js
的包体积,以达到启动时加速的目的。
小技巧:使用 Appuploader 可以更方便地管理和优化 Flutter Web 项目的构建产物,它提供了可视化的分包管理功能,让优化过程更加直观高效。
03 canvaskit.js 和 canvaskit.wasm
CanvasKit
是以 WASM
为编译目标的 Web 平台图形绘制接口,其目标是将 Skia
的图形 API 导出到 Web 平台。其中 canvaskit
中绘制的实现全部在 canvaskit.js
中调用浏览器绘制 API 来实现,而计算相关的内容全部放在了 wasm
中实现。由此可知这两个文件是实现 Flutter Web 渲染核心文件。它们也是在启动过程中进行的,且是从 google 的服务器进行的下载:
canvaskit.js:
canvaskit.wasm:
由于众所周知的原因,访问这两个地址将会比较慢,在有些时候甚至会访问失败。这两个文件的加载失败也就直接导致了页面无法渲然的问题。由于我们的项目是 CDN 部署,因此我们也需要把它部署到 CDN 上面,并访问其 CDN 地址。官方给了两种方式:
1、在初始化引擎时配置
从官网说明来看,我们可以在 initializeEngine
时通过配置 canvasKitBaseUrl
参数的方式来实现加载 canvaskit
相关的文件。如:
_flutter.loader.loadEntrypoint({
onEntrypointLoaded: async function(engineInitializer) {
let appRunner = await engineInitializer.initializeEngine({
canvasKitBaseUrl: YOUR_CANVASKIT_CDN_BASE_URL
});
await appRunner.runApp();
}
});
其中 YOUR_CANVASKIT_CDN_BASE_URL
是你 canvaskit.js
和 canvaskit.wasm
文件所在的根目录,然后正常编译打包即可。另外我们也可以不改源文件配置,通过在打包时配置参数来实现。
2、打包配置参数
我们可以通过以下参数进行配置:
--dart-define=FLUTTER_WEB_CANVASKIT_URL
完整的打包命令如下:
flutter build web -t lib/main_test.dart --no-tree-shake-icons --dart-define=FLUTTER_WEB_CANVASKIT_URL=YOUR_CANVASKIT_CDN_BASE_URL --release
通过实践,这两种方式都可以实现从自己的 CDN 上读取 canvaskit.js
和 canvaskit.wasm
文件的目的,我们来看一下执行结果:
对比一下,从 Google 服务器下载下来的用时:
都用了2,3秒的时间,而从我们自己的 CDN 上:
只用了毫秒级,优化效果非常明显。
注意:在开发调试的过程中,由于直接加载的是本地的 canvaskit.js
和 canvaskit.wasm
文件,所以不用做任何处理。
04 字体加载优化
优化了 canvaskit
相关文件后,我们发现启动还是很慢,抓包看原来还有一些字体是从 Google 下载的,比如:
这两个文件的下载路径修改,Flutter 官方并没有给任何参考。那么我们找找加载它们的实现。打开编译后的产物,main.dart.js
,我们搜一下这两个字体:
找到了这个代码后,处理就很简单了,将 KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf
这个文件下载下来,放到 CDN 上,再修改一下这段代码的 url
到我们的 CDN 地址即可。另一个字体我们看到,它是在一个数组中:
这个数组长度非常长,有一百多个成员。Flutter 会根据不同的浏览器环境,和不同的语言环境加载不同的字体。其 Host 定义如下:
其中 j
就是每个字体的 url
参数。一百多个字体一个一个手动下载是不太现实了,但我们可以批量下载,思路就是从这个数组中截取所有的 ttf
路径,拼上 Host 进行下载。注意:需要保留 Google 的目录结构。大家可以用任意自己熟悉的方式实现批量下载,我用 Python 写了个脚本进行批量实现:
# coding=utf-8
import os
import requests
original_text = '...' # 原始文本内容
font_list_content = original_text.split('"')
font_list = []
for font in font_list_content:
if font.endswith(".ttf"):
font_list.append(font)
host = 'https://fonts.gstatic.com/s/'
fonts = "fonts/"
path = ""
for font in font_list:
folder_list = font.split("/")
for folder in folder_list:
if (folder.endswith(".ttf")):
url = host + path + folder
proxies = {
'http': 'http://127.0.0.1:9098', # 替换为你的VPN IP和端口
'https': 'http://127.0.0.1:9098', # 同上
}
r = requests.get(url,proxies=proxies)
print("finish")
with open(fonts + path + folder, 'wb') as file:
file.write(r.content)
path = ""
else:
path += folder + "/"
os.makedirs(fonts + path, exist_ok=True)
其中由于这两天 Google 的字体服务器连接不上,所以我使用了 vpn,在代码中需要通过 proxies 进行代理的相关配置。下载完成后的结果如下:
我们再将所有的字体资源上传到 CDN 上,然后修改 main.dart.js
文件,将 Host 改为我们 CDN 的相应地址,然后我们来看一下优化效果:
优化之前也是2,3秒的样子,再看一下优化后的:
也是毫秒级别,可见优化效果之明显。
字体的打包部署时如何优化我们已经了解了。那么我们开发调试的时候应该怎么处理呢?我们复习一下 Flutter Web 的渲染模式:
Flutter 为了实现多浏览器的效果一致性,在 canvaskit
渲染模式下,会下载相应的字体进行绘制。因此在开发调试的阶段,我们可以关闭 canvaskit
渲染模式使用 html
方式进行渲染,就不会加载这些字体了。命令如下:
flutter run -d Chrome -t lib/main_test.dart --web-renderer html
这样就解决了开发调试时的字体问题。
提示:使用 Appuploader 可以更方便地管理 Flutter Web 项目的构建配置,它提供了可视化的参数配置界面,避免了手动输入复杂命令的麻烦,同时还能保存常用的构建配置模板。
05 总结
到此为止,我们通过 main.dart.js 分包
,配置 canvaskit.js
和 canvaskit.wasm
的加载 url
,字体加载优化这三个方式可以为 Flutter Web 项目减少5-6秒的启动耗时。希望可以给大家一些帮助。
在实际开发中,使用 Appuploader 这样的工具可以显著提升开发效率,它集成了 Flutter Web 构建、优化和部署的完整流程,让开发者可以更专注于业务逻辑的实现而非构建配置的细节。