flutter web编译瘦身
# flutter web编译瘦身
# 缩减字体文件
# 美团的 图标字体精简
当访问 FlutterWeb 页面时,即使在业务代码中并未使用 Icon 图标,也会加载一个 920KB 的图标字体文件:MaterialIcons-Regular.woff。通过探究,我们发现是 Flutter Framework 中一些系统 UI 组件(如:CalendarDatePicker、PaginatedDataTable、PopupMenuButton 等)使用到了 Icon 图标导致,且 Flutter 为了便于开发者使用,提供了全量的 Icon 图标字体文件。
Flutter 官方提供的 --tree-shake-icons
命令选项是将业务使用到的 Icon 与 Flutter 内部维护的一个缩小版字体文件(大约 690KB)进行合并,能一定程度上减小字体文件大小。而我们需要的是只打包业务使用的 Icon,所以我们对官方 tree-shake-icons
进行了优化,设计了 Icon 的按需打包方案:
图23 图标字体精简
- 扫描全部业务代码以及依赖的 Plugins、Packages、Flutter Framework,分析出所有用到的 Icon;
- 把扫描到的所有 Icon 与 material/icons.dart(该文件包含 Flutter Icon 的 unicode 编码集合)进行对比,得到精简后的图标编码列表:iconStrList;
- 使用 FontTools 工具把 iconStrList 生成字体文件 .woff,此时的字体文件仅包含真正使用到的 Icon。
# 使用
flutter build web --web-renderer html --tree-shake-icons
# 报错
This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:
- file:///Users/hss/dev/flutter-3.7.0/packages/flutter/lib/src/widgets/icon_data.dart:20:9
- file:///Users/hss/.pub-cache/hosted/pub.dev/font_awesome_flutter-10.4.0/lib/src/icon_data.dart:9:9
- file:///Users/hss/.pub-cache/hosted/pub.dev/font_awesome_flutter-10.4.0/lib/src/icon_data.dart:21:9
- file:///Users/hss/.pub-cache/hosted/pub.dev/font_awesome_flutter-10.4.0/lib/src/icon_data.dart:33:9
- file:///Users/hss/.pub-cache/hosted/pub.dev/font_awesome_flutter-10.4.0/lib/src/icon_data.dart:46:9
- file:///Users/hss/.pub-cache/hosted/pub.dev/font_awesome_flutter-10.4.0/lib/src/icon_data.dart:66:9
- file:///Users/hss/.pub-cache/hosted/pub.dev/font_awesome_flutter-10.4.0/lib/src/icon_data.dart:79:9
Target web_release_bundle failed: Exception: Avoid non-constant invocations of IconData or try to build again with --no-tree-shake-icons.
2
3
4
5
6
7
8
9
10
官方issuse: https://github.com/flutter/flutter/issues/57181
# 解决方式: 编译Android,然后替换调web编译里的字体文件:
For those who are looking for a quick fix can try this (opens new window) approach.
Step 1: Release an apk file with tree shake by using flutter build apk --release --tree-shake-icons
Step 2: Go to /build/app/intermediates/assets/release/mergeReleaseAssets/flutter_assets/fonts/MaterialIcons-Regular.otf
Step 3: Make sure it's size is in KBs
Step 4: Copy that file and keep it on your desktop
Step 5: Build your web app and replace the large file web/assets/fonts/MaterialIcons-Regular.otf
_This droped my icon size from 1.6MB to 2KB I used only 10 icons in the whole project
Tips: For a quick action, try to automate this process using makefile (opens new window)
Hope this helps someone.
# 一键打包和替换脚本:
import 'dart:io';
import 'package:process_run/shell.dart';
/// 终极形态: 一键发布所有包,不用手动发布
main() async {
//buildAndroid();
//packMacos();
packWeb();
}
Future<void> packWeb() async {
String fontPath = "${Directory.current.path}/build/app/intermediates/assets/release/mergeReleaseAssets/flutter_assets/fonts/MaterialIcons-Regular.otf";
File file = File(fontPath);
if(!file.existsSync()){
print("android 打包treeshake的字体文件不存在,重新打包Android");
await buildAndroid();
print("android 打包成功,开始继续打包web");
packWeb();
}else{
print("android 打包treeshake的字体文件存在,直接打包web,打包后拷贝字体");
await buildWeb();
//web/assets/fonts/MaterialIcons-Regular.otf
File target = File("${Directory.current.path}/build/web/assets/fonts/MaterialIcons-Regular.otf");
file.copySync(target.path);
print("拷贝字体文件成功: ${target.path}");
//todo windows上,将web文件夹打包成zip,然后部署到服务器上
}
}
Future<void> buildWeb(){
return exeShell("flutter build web --web-renderer html");
//--tree-shake-icons: 会报错. 解决方式: 先编译Android,然后编译web,将Android里的字体icon覆盖掉web里
}
Future<void> buildAndroid(){
return exeShell("flutter build apk --release --tree-shake-icons");
}
Future<void> buildMacReal() async {
await buildMacOs();
packMacos();
}
Future<void> buildMacOs(){
return exeShell("flutter build macos --release --tree-shake-icons");
}
Future<void> packMacos(){
return exeShell("hdiutil create -volname chatgpt -srcfolder ${Directory.current.path}/build/macos/Build/Products/Release/MyChatAI.app"
" -ov -format UDZO ${getPCUserPath()}/Downloads/MyChatAI-release.dmg");
}
String getPCUserPath() {
//var platform = Platform.isWindows ? 'win32' : 'linux';
var homeDir = Platform.environment['HOME'];
//var userDir = Platform.environment['USERPROFILE'] ?? '$homeDir\\AppData\\Local';
// return Directory('$userDir\\$platform\\flutter').path; // 此处以 Flutter 为例
return homeDir??"/";
}
Future<void> exeShell(String cmd) async {
var shell = Shell(runInShell: true);
try{
await shell.run(cmd);
}catch(e,s){
//print("error1:"+e.toString());
throw e;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84