这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战
flutter web 首页加载优化
目前存在的问题:
1.功能无法及时更新:浏览器对同名文件的缓存,可能导致程序代码不被及时更新或者执行错乱。
2.首屏渲染性能差:main.dart.js 文件过大,单一文件解析加载时间过长,影响首屏渲染
3.无法使用CDN:Flutter 仅支持相对路径的加载方式,无法使用当前域名以外的CDN域名,导致无法享受CDN带来的优势
功能无法及时更新
功能无法及时更新时因为浏览器对同名文件的缓存,可能导致程序代码不被及时更新或者执行错乱
Flutter 编译过程
Flutter build web -> flutter engine 源码 -> AOT产物 -> 中间产物 -> 静态资源
我们在AOT产物形成之前做优化,从以下三个方面解决:
-
资源文件hash化
-
大文件分片
-
资源文件CDN化
资源文件hash化
除了 web/index.html 文件之外,我们要对所有的引用到文件进行 Hash 化。对 build_system/web.dart 的修改按以下步骤进行:
- 遍历产物目录,并建立
ResourceMap。 - 分别计算每个文件的
Hash值。 - 为新文件命名为
name-[hash].xxx。 - 修改新文件名在对应文件中的引用关系。
查看build_system/web.dart源码,没有找到合适的添加地方
暂采用Python脚本实现资源hash化,整个过程也是一样
1.遍历目录,找到dart.js 、part.js结尾的文件
2.计算hash值
3.拼接处带有hash值的文件名
4.替换main.dart.js / index.html / flutter_service_worker.js文件中的文件名,换成带有hash值的
5.将part.js文件在index.html中主动声明
6.将dart.js 、part.js结尾的文件的文件名称更改为带hash值的
7.压缩
具体脚本如下:
#!/usr/bin/python
# coding:utf-8
import hashlib
import os, sys
import zipfile
print("\n ======== Start hash resource ======== \n")
#计算md5值
def CalcMD5(filepath):
with open(filepath,'rb') as f:
md5obj = hashlib.md5()
md5obj.update(f.read())
hash = md5obj.hexdigest()
return hash
#给文件名加hash值
def createHashName(oldFileName):
hash = CalcMD5(oldFileName)
if (oldFileName.endswith('dart.js')):
return oldFileName.replace('dart.js',hash+'.dart.js')
if (oldFileName.endswith('part.js')):
return oldFileName.replace('part.js',hash+'.part.js')
# 查看当前工作目录
currentPath = os.getcwd()
# 修改当前工作目录
path = "./build/web"
os.chdir( path )
webPath = os.getcwd()
print("找到.js结尾的文件并拼接hash")
jsFileList = []
jsFileHashMap = {}
pathList = os.listdir(webPath)
for path in pathList:
if (path.endswith("dart.js") | path.endswith("part.js")):
jsFileList.append(path)
jsFileHashMap[path] = createHashName(path)
print(jsFileHashMap)
#检查文件,替换文件中的一些文件引用
def checkFile(file):
with open(file, "r") as f1,open("%s.bak" % file, "w") as f2:
for line in f1:
for old_str in jsFileList:
if old_str in line:
if old_str == 'main.dart.js':
if 'part.js' in line:
continue
new_str = jsFileHashMap[old_str]
line = line.replace(old_str, new_str)
lastLine = line
f2.write(line)
os.remove(file)
os.rename("%s.bak" % file, file)
print("整理main.dart.js")
mainDartJs = 'main.dart.js'
checkFile(mainDartJs)
print("整理flutter_service_worker.js")
flutterServiceWorkerJs = 'flutter_service_worker.js'
checkFile(flutterServiceWorkerJs)
print("整理index.html")
indexHtml = 'index.html'
checkFile(indexHtml)
print("将part.js的引用手动添加到index.html中")
partJsFile = []
for jsFile in jsFileList:
if (jsFile.endswith('part.js')):
partJsFile.append(jsFileHashMap[jsFile])
with open(indexHtml, "r") as f1,open("%s.bak" % indexHtml, "w") as f2:
for line in f1:
if "js/plugin.js" in line :
f2.write(line)
for partFile in partJsFile:
f2.write("<script src=\"%s\" type=\"text/javascript\"></script>\n" % partFile)
continue
f2.write(line)
os.remove(indexHtml)
os.rename("%s.bak" % indexHtml, indexHtml)
print("更改文件名")
for jsFile in jsFileList :
newFile = jsFileHashMap[jsFile]
os.rename(jsFile,newFile)
print("返回上层文件夹,更改web文件为static-meeting-manager并压缩")
managerFile = "static-meeting-manager"
os.chdir('..')
os.rename('web',managerFile)
zipFile = "static-meeting-manager.zip"
if (os.path.exists(zipFile)):
os.remove(zipFile)
zip = zipfile.ZipFile(zipFile,"w")
for path,dirnames,filenames in os.walk(managerFile):
zip.write(path)
for filename in filenames:
zip.write(os.path.join(path,filename))
zip.close()
print("\n========= 操作完成 ===========\n")
首屏渲染性能差
flutter web 编译过后会将所有代码都生成在main.dart.js文件中,这就导致main.dart.js文件过大,从而导致加载慢
解决方法:
tree shaking优化- 延迟加载
tree shaking 优化
初次加载时间可以通过最小化JS包体积来实现,tree shaking 和延迟加载都可以最大程度减少JS包体积
tree shaking 是只将一定会执行的代码打包进来,从而剔除无用代码的过程。
默认打release包时就开启了 tree shaking
延迟加载
延迟加载也叫懒加载,就是允许在需要是才去加载各种库。延迟加载是一个dart2JS特性,所以只能在Flutter web上用。
使用方式
1.首先将包或者文件引入为deferred
2.配合FutureBuilder来切换加载UI
import 'my_app.dart' deferred as myApp;
void main() async {
runApp(AssetsApp(
child: FutureBuilder(
future: myApp.loadLibrary(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return myApp.MyApp();
} else {
return Center(
child: Padding(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(),
),
);
}
})));
}
优化参考: