作者:马坤乐(坤吾)
Flutter for Web(FFW)从 2021 年发布至今,在国内外互联网公司已经得到较多的应用。作为 Flutter 技术在 Web 领域的有力扩充,FFW 可以让熟悉 Flutter 的客户端同学直接上手写 H5,复用 App 端代码高效支撑业务需求;在 App 侧 FFW 也可作为 Flutter 动态下发的兜底方案。总的来说在业务和技术上 FFW 都具有相当的价值。
然而在使用 FFW 时有一个明显的问题:其编译产物 main.dart.js
较大,初始的 Hello world 工程编译后产物 js 大小为 1.2 MB,添加业务代码后 js 的大小还会继续增加。在阿里卖家的内容外投业务中,3 个页面的工程 js 大小为 2.0 MB,js 文件过大直接的影响就是页面首次首屏加载的速度。针对 js 的大小有较多优化方法,本文主要记录 main.dart.js
分片优化方案的实现。
1.方案总览
页面 js 加载速度提升一般从两个角度考虑:
- 减少 js 文件大小
- 提升 js 加载效率
对应到 js 分片方案,主要通过如下两点提升加载速度:
按需加载:在工程中存在多个页面时,不论打开哪个页面都需要加载完整的main.dart.js
,而这里包含了很多不需要的页面代码。如果将各个页面的代码拆分只加载当前页面所需要的代码,则可减少 js 文件体积,而且当其他页面越多逻辑越复杂时,其提升的效果越明显。
并行加载:将 js 分片后会生成多个大小不一的 js 文件,在带宽充足的情况下如果使用并行加载则可以节省较小的分片加载时间。
注:js 文件压缩在线上部署的时候会自动处理,这里不做处理。
2. 工程实践
通过按需和并行加载提升加载速度,首先需要完成 js 的分片。分片和按需加载操作通常是绑定的,如在前端 Vue 开发中,可使用 webpack 的 code splitting 工具在定义好各类库的使用关系后实现文件分割和按需加载,类似的在 flutter 中则可使用 延迟加载组件 功能。
2.1 延迟加载组件
Flutter 为 App 设计的延迟组件加载功能同样适用于 FFW。在 dart 代码中通过关键字 deffered as
引入相关代码库并在使用时加载即可实现延迟加载功能。在官方的示例中可以通过如下的方式实现 box.dart
的延迟加载。
// box.dart
import 'package:flutter/material.dart';
/// 一个正常方式编写的 widget,后面会被延迟加载
class DeferredBox extends StatelessWidget {
const DeferredBox({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 30,
width: 30,
color: Colors.blue,
);
}
}
在需要使用 box.dart
的地方通过 deferred as
关键字引入 box.dart
/// some_widget.dart
import 'package:flutter/material.dart';
/// 1. deferred as 引入
import 'box.dart' deferred as box;
class SomeWidget extends StatefulWidget {
const SomeWidget({Key? key}) : super(key: key);
@override
State<SomeWidget> createState() => _SomeWidgetState();
}
之后调用延迟加载库的加载方法,加载完成后使用即可
/// some_widget.dart
class _SomeWidgetState extends State<SomeWidget> {
late Future<void> _libraryFuture;
@override
void initState() {
/// 2. 使用时加载延迟加载库
_libraryFuture = box.loadLibrary();
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<void>(
future: _libraryFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {return Text('Error: ${snapshot.error}');}
/// 3. 延迟加载库加载完成后使用
return box.DeferredBox();
}
return const CircularProgressIndicator();
},
);
}
}
经过上述操作后,在 FFW 中编译后可生成类似如下的两个 js 文件:
├── [1.2M] main.dart.js /// FFW 引擎和主工程内容
├── [616B] main.dart.js_1.part.js /// 存放 box.dart 对应的内容
在多页面的