Bootstrap

FlutterWeb启动耗时优化

80e5d4475a2df8b366c44a9d0037535b.jpeg

d8d03b36f02b21e608fcf66cff3617c1.gif

本文字数:4307

预计阅读时间:30分钟

01

前言

在之前的文章中,我们详细的介绍过Flutter Web从0到部署上线的流程,并且针对其中遇到的问题进行了分析与修复:https://www.jianshu.com/p/6213f8a88288

我们也对Flutter Web的项目进行了打包部署上线。在产品体验中会发现,跟传统的web端网页对比,还是明显感觉到性能上的差异,特别是启动的过程明显耗时较长。本文将针对Flutter Web项目的做一些启动上的优化和探讨。SDK版本:Flutter SDK 3.19.0

02

main.dart.js分包

main.dart.jsFlutter Web的编译产物,所有的业务逻辑都在这一个js文件里,可想而知它会比较臃肿。在Flutter Web发布后,就有了main.dart.js分包的需求,看了很多网上的案例,使用deferred-components即可达成分包的目的。但在写上一篇文章的时候,我们用的是Flutter SDK 3.10.5,按照官网的步骤进行了分包处理发现编译是成功的,但是最终并没有实现分包。阅读官网文档,当时明确说明了deferred-components只能用于Android的编译产物。猜测那时候可能发现了Web分包的一些问题,导致在某一些版本的SDK并不支持Webdeferred-components。链接如下:https://flutter.cn/docs/perf/deferred-components 

现在重新阅读官网,描述已经进行了修改:

FlutterAndroidWeb 上支持延迟加载或「懒」加载。在不同平台实现方式有所不同。Android 平台的 动态功能模块 提供打包为 Android module 的延迟组件。Web 平台将这些组件创建为单独的 *.js 文件。延迟组件中的代码不会影响其他平台,其他平台在初始安装时会正常构建包含所有延迟组件和资源的应用。

看上去目前deferred-componentsWeb端已经做到了很好的支持,那么我们就实验一下,步骤如下:

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;

  @override
  void initState() {
    super.initState();
    _libraryFuture = achievement.loadLibrary();
  }

最后使用FutureBuilder等待loadLibraryFuture对象初始化完成,并实现延迟加载:

@override
  Widget build(BuildContext context) {
    return FutureBuilder<void>(
      future: _libraryFuture,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
        return achievement.MeReadAchievementWidget();
        }
        return const CircularProgressIndicator();
      },
    );
  }

最后再进行打包。

3、编译产物

打包完成后我们看下编译产物,会发现除了main.dart.js之外又多了一个main.dart.js_1.part.js

8f4d6fe2248ed238ef5c743a8b7752db.jpegmain.dart.js_1.part.js即为分包。 

4、分包加载

部署之后我们打开浏览器看一下效果:

在启动Web页面后我们发现只加载了main.dart.js3dd8410d9cd5df5c6acbcf34f77d71e6.jpeg

只有操作到相应的页面,才会加载main.dart.js_1.part.js

41d5dd3c509f444ff7eb88876f69620e.jpeg

这说明如果业务比较复杂页面很多的情况下,我们可以根据自己的需求和体验细化项目的结构,将Widget进行拆分,在需要的时候再进行延迟加载,一定程度上减少main.dart.js的包体积,以达到启动时加速的目的。

03

canvaskit.js和canvaskit.wasm

CanvasKit是以WASM为编译目标的Web平台图形绘制接口,其目标是将Skia的图形API导出到Web平台。其中canvaskit中绘制的实现全部在canvaskit.js中调用浏览器绘制API来实现,而计算相关的内容全部放在了wasm中实现。由此可知这两个文件是实现Flutter Web渲染核心文件。它们也是在启动过程中进行的,且是从google的服务器进行的下载:

canvaskit.js:

f7deec778d67f64bd68ca8b7a18b257c.jpeg

canvaskit.wasm:

a67353d9d0d6d57cbee945e0474b6369.jpeg

由于众所周知的原因,访问这两个地址将会比较慢,在有些时候甚至会访问失败。这两个文件的加载失败也就直接导致了页面无法渲然的问题。由于我们的项目是CDN部署,因此我们也需要把它部署到CDN上面,并访问其CDN地址。官方给了两种方式:

1、在初始化引擎时配置

976a69786585813bd124097bad0e058c.jpeg从官网说明来看,我们可以在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.jscanvaskit.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.jscanvaskit.wasm文件的目的,我们来看一下执行结果:a6b28620cdcac934db7bdfbfc4c7d487.jpegef9e306defd6328123918d6ac60a262a.jpeg

对比一下,从Google服务器下载下来的用时:

edab296f2c561917615aa0882febc485.jpegcba4a2e4d4e05b492f115f635546210b.jpeg

都用了2,3秒的时间,而从我们自己的CDN上:0e181ecd3e4a3141bd9da764b6250352.jpeg450768a8499d900f332a284de670aa73.jpeg只用了毫秒级,优化效果非常明显。 

好的,接下来我们发现,还有一些字体文件会从Google的服务器进行下载,接下来我们优化一下字体的问题。

注意:在开发调试的过程中,由于直接加载的是本地的canvaskit.jscanvaskit.wasm文件,所以不用做任何处理。

04

字体加载优化

优化了canvaskit相关文件后,我们发现启动还是很慢,抓包看原来还有一些字体是从Google下载的,比如:05ef9b217a176aabe1a63e038cad8461.jpega4a1d0960ea91ab282cfb9d82bb0e416.jpeg

这两个文件的下载路径修改,Flutter官方并没有给任何参考。那么我们找找加载它们的实现。打开编译后的产物,main.dart.js,我们搜一下这两个字体:db557bee0e608e7e62843f6f829cf0a2.jpeg

找到了这个代码后,处理就很简单了,将KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf这个文件下载下来,放到CDN上,再修改一下这段代码的url到我们的CDN地址即可。另一个字体我们看到,它是在一个数组中:ed91adbc68d7cb29019c609102cde5cb.jpeg

这个数组长度非常长,有一百多个成员。Flutter会根据不同的浏览器环境,和不同的语言环境加载不同的字体。其Host定义如下:e3be40d784ac2be7f7b5b24a5f682f98.jpeg

其中j就是每个字体的url参数。一百多个字体一个一个手动下载是不太现实了,但我们可以批量下载,思路就是从这个数组中截取所有的ttf路径,拼上Host进行下载。注意:需要保留Google的目录结构。大家可以用任意自己熟悉的方式实现批量下载,我用Python写了个脚本进行批量实现:

# coding=utf-8

import os

import requests

original_text = 'r=this.b=A.cOH(new A.bs4(this),A.a([A.aY("Noto Sans","notosans/v32/o-0IIpQlx3QUlC5A4PNb4j5Ba_2c7A.ttf",!0),A.aY("Noto Color Emoji","notocoloremoji/v25/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFab5s79iz64w.ttf",s),A.aY("Noto Emoji","notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf",!s),A.aY("Noto Music","notomusic/v20/pe0rMIiSN5pO63htf1sxIteQB9Zra1U.ttf",!0),A.aY("Noto Sans Symbols","notosanssymbols/v41/rP2up3q65FkAtHfwd-eIS2brbDN6gxP34F9jRRCe4W3gfQ8gavVFRkzrbQ.ttf",!0),A.aY("Noto Sans Symbols 2","notosanssymbols2/v22/I_uyMoGduATTei9eI8daxVHDyfisHr71ypPqfX71-AI.ttf",!0),A.aY("Noto Sans Adlam","notosansadlam/v22/neIczCCpqp0s5pPusPamd81eMfjPonvqdbYxxpgufnv0TGnBZLwhuvk.ttf",!0),A.aY("Noto Sans Anatolian Hieroglyphs","notosansanatolianhieroglyphs/v16/ijw9s4roRME5LLRxjsRb8A0gKPSWq4BbDmHHu6j2pEtUJzZWXybIymc5QYo.ttf",!0),A.aY("Noto Sans Arabic","notosansarabic/v18/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvu3CBFQLaig.ttf",!0),A.aY("Noto Sans Armenian","notosansarmenian/v42/ZgN0jOZKPa7CHqq0h37c7ReDUubm2SEdFXp7ig73qtTY5idb74R9UdM3y2nZLorxb60iYy6zF3Eg.ttf",!0),A.aY("Noto Sans Avestan","notosansavestan/v21/bWti7ejKfBziStx7lIzKOLQZKhIJkyu9SASLji8U.ttf",!0),A.aY("Noto Sans Balinese","notosansbalinese/v24/NaPwcYvSBuhTirw6IaFn6UrRDaqje-lpbbRtYf-Fwu2Ov7fdhE5Vd222PPY.ttf",!0),A.aY("Noto Sans Bamum","notosansbamum/v27/uk-0EGK3o6EruUbnwovcbBTkkklK_Ya_PBHfNGTPEddO-_gLykxEkxA.ttf",!0),A.aY("Noto Sans Bassa Vah","notosansbassavah/v17/PN_bRee-r3f7LnqsD5sax12gjZn7mBpL5YwUpA2MBdcFn4MaAc6p34gH-GD7.ttf",!0),A.aY("Noto Sans Batak","notosansbatak/v19/gok2H6TwAEdtF9N8-mdTCQvT-Zdgo4_PHuk74A.ttf",!0),A.aY("Noto Sans Bengali","notosansbengali/v20/Cn-SJsCGWQxOjaGwMQ6fIiMywrNJIky6nvd8BjzVMvJx2mcSPVFpVEqE-6KmsolLudCk8izI0lc.ttf",!0),A.aY("Noto Sans Bhaiksuki","notosansbhaiksuki/v17/UcC63EosKniBH4iELXATsSBWdvUHXxhj8rLUdU4wh9U.ttf",!0),A.aY("Noto Sans Brahmi","notosansbrahmi/v18/vEFK2-VODB8RrNDvZSUmQQIIByV18tK1W77HtMo.ttf",!0),A.aY("Noto Sans Buginese","notosansbuginese/v18/esDM30ldNv-KYGGJpKGk18phe_7Da6_gtfuEXLmNtw.ttf",!0),A.aY("Noto Sans Buhid","notosansbuhid/v22/Dxxy8jiXMW75w3OmoDXVWJD7YwzAe6tgnaFoGA.ttf",!0),A.aY("Noto Sans Canadian Aboriginal","notosanscanadianaboriginal/v22/4C_TLjTuEqPj-8J01CwaGkiZ9os0iGVkezM1mUT-j_Lmlzda6uH_nnX1bzigWLn_yAsg0q0uhQ.ttf",!0),A.aY("Noto Sans Carian","notosanscarian/v16/LDIpaoiONgYwA9Yc6f0gUILeMIOgs7ob9yGLmfI.ttf",!0),A.aY("Noto Sans Caucasian Albanian","notosanscaucasianalbanian/v16/nKKA-HM_FYFRJvXzVXaANsU0VzsAc46QGOkWytlTs-TXrYDmoVmRSZo.ttf",!0),A.aY("Noto Sans Chakma","notosanschakma/v17/Y4GQYbJ8VTEp4t3MKJSMjg5OIzhi4JjTQhYBeYo.ttf",!0),A.aY("Noto Sans Cham","notosanscham/v29/pe06MIySN5pO62Z5YkFyQb_bbuRhe6D4yip43qfcERwcv7GykboaLg.ttf",!0),A.aY("Noto Sans Cherokee","notosanscherokee/v20/KFOPCm6Yu8uF-29fiz9vQF9YWK6Z8O10cHNA0cSkZCHYWi5PDkm5rAffjl0.ttf",!0),A.aY("Noto Sans Coptic","notosanscoptic/v20/iJWfBWmUZi_OHPqn4wq6kgqumOEd78u_VG0xR4Y.ttf",!0),A.aY("Noto Sans Cuneiform","notosanscuneiform/v17/bMrrmTWK7YY-MF22aHGGd7H8PhJtvBDWgb9JlRQueeQ.ttf",!0),A.aY("Noto Sans Cypriot","notosanscypriot/v15/8AtzGta9PYqQDjyp79a6f8Cj-3a3cxIsK5MPpahF.ttf",!0),A.aY("Noto Sans Deseret","notosansdeseret/v17/MwQsbgPp1eKH6QsAVuFb9AZM6MMr2Vq9ZnJSZtQG.ttf",!0),A.aY("Noto Sans Devanagari","notosansdevanagari/v25/TuGoUUFzXI5FBtUq5a8bjKYTZjtRU6Sgv3NaV_SNmI0b8QQCQmHn6B2OHjbL_08AlXQly-AzoFoW4Ow.ttf",!0),A.aY("Noto Sans Duployan","notosansduployan/v17/gokzH7nwAEdtF9N8-mdTDx_X9JM5wsvrFsIn6WYDvA.ttf",!0),A.aY("Noto Sans Egyptian Hieroglyphs","notosansegyptianhieroglyphs/v28/vEF42-tODB8RrNDvZSUmRhcQHzx1s7y_F9-j3qSzEcbEYindSVK8xRg7iw.ttf",!0),A.aY("Noto Sans Elbasan","notosanselbasan/v16/-F6rfiZqLzI2JPCgQBnw400qp1trvHdlre4dFcFh.ttf",!0),A.aY("Noto Sans Elymaic","notosanselymaic/v15/UqyKK9YTJW5liNMhTMqe9vUFP65ZD4AjWOT0zi2V.ttf",!0),A.aY("Noto Sans Georgian","notosansgeorgian/v42/PlIaFke5O6RzLfvNNVSitxkr76PRHBC4Ytyq-Gof7PUs4S7zWn-8YDB09HFNdpvnzFj-f5WK0OQV.ttf",!0),A.aY("Noto Sans Glagolitic","notosansglagolitic/v17/1q2ZY4-BBFBst88SU_tOj4J-4yuNF_HI4ERK4Amu7nM1.ttf",!0),A.aY("Noto Sans Gothic","notosansgothic/v16/TuGKUUVzXI5FBtUq5a8bj6wRbzxTFMX40kFQRx0.ttf",!0),A.aY("Noto Sans Grantha","notosansgrantha/v17/3y976akwcCjmsU8NDyrKo3IQfQ4o-r8cFeulHc6N.ttf",!0),A.aY("Noto Sans Gujarati","notosansgujarati/v23/wlpWgx_HC1ti5ViekvcxnhMlCVo3f5pv17ivlzsUB14gg1TMR2Gw4VceEl7MA_ypFwPM_OdiEH0s.ttf",!0),A.aY("Noto Sans Gunjala Gondi","notosansgunjalagondi/v19/bWtX7e7KfBziStx7lIzKPrcSMwcEnCv6DW7n5g0ef3PLtymzNxYL4YDE4J4vCTxEJQ.ttf",!0),A.aY("Noto Sans Gurmukhi","notosansgurmukhi/v26/w8g9H3EvQP81sInb43inmyN9zZ7hb7ATbSWo4q8dJ74a3cVrYFQ_bogT0-gPeG1OenbxZ_trdp7h.ttf",!0),A.aY("Noto Sans HK","notosanshk/v31/nKKF-GM_FYFRJvXzVXaAPe97P1KHynJFP716qHB--oWTiYjNvVA.ttf",!0),A.aY("Noto Sans Hanunoo","notosanshanunoo/v20/f0Xs0fCv8dxkDWlZSoXOj6CphMloFsEsEpgL_ix2.ttf",!0),A.aY("Noto Sans Hatran","notosanshatran/v16/A2BBn4Ne0RgnVF3Lnko-0sOBIfL_mM83r1nwzDs.ttf",!0),A.aY("Noto Sans Hebrew","notosanshebrew/v43/or3HQ7v33eiDljA1IufXTtVf7V6RvEEdhQlk0LlGxCyaeNKYZC0sqk3xXGiXd4qtoiJltutR2g.ttf",!0),A.aY("Noto Sans Imperial Aramaic","notosansimperialaramaic/v16/a8IMNpjwKmHXpgXbMIsbTc_kvks91LlLetBr5itQrtdml3YfPNno.ttf",!0),A.aY("Noto Sans Indic Siyaq Numbers","notosansindicsiyaqnumbers/v16/6xK5dTJFKcWIu4bpRBjRZRpsIYHabOeZ8UZLubTzpXNHKx2WPOpVd5Iu.ttf",!0),A.aY("Noto Sans Inscriptional Pahlavi","notosansinscriptionalpahlavi/v16/ll8UK3GaVDuxR-TEqFPIbsR79Xxz9WEKbwsjpz7VklYlC7FCVtqVOAYK0QA.ttf",!0),A.aY("Noto Sans Inscriptional Parthian","notosansinscriptionalparthian/v16/k3k7o-IMPvpLmixcA63oYi-yStDkgXuXncL7dzfW3P4TAJ2yklBJ2jNkLlLr.ttf",!0),A.aY("Noto Sans JP","notosansjp/v52/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj75vY0rw-oME.ttf",!0),A.aY("Noto Sans Javanese","notosansjavanese/v23/2V01KJkDAIA6Hp4zoSScDjV0Y-eoHAHT-Z3MngEefiidxJnkFFliZYWj4O8.ttf",!0),A.aY("Noto Sans KR","notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLTq8H4hfeE.ttf",!0),A.aY("Noto Sans Kaithi","notosanskaithi/v20/buEtppS9f8_vkXadMBJJu0tWjLwjQi0KdoZIKlo.ttf",!0),A.aY("Noto Sans Kannada","notosanskannada/v26/8vIs7xs32H97qzQKnzfeXycxXZyUmySvZWItmf1fe6TVmgop9ndpS-BqHEyGrDvNzSIMLsPKrkY.ttf",!0),A.aY("Noto Sans Kayah Li","notosanskayahli/v21/B50nF61OpWTRcGrhOVJJwOMXdca6Yecki3E06x2jVTX3WCc3CZH4EXLuKVM.ttf",!0),A.aY("Noto Sans Kharoshthi","notosanskharoshthi/v16/Fh4qPiLjKS30-P4-pGMMXCCfvkc5Vd7KE5z4rFyx5mR1.ttf",!0),A.aY("Noto Sans Khmer","notosanskhmer/v23/ijw3s5roRME5LLRxjsRb-gssOenAyendxrgV2c-Zw-9vbVUti_Z_dWgtWYuNAJz4kAbrddiA.ttf",!0),A.aY("Noto Sans Khojki","notosanskhojki/v18/-nFnOHM29Oofr2wohFbTuPPKVWpmK_d709jy92k.ttf",!0),A.aY("Noto Sans Khudawadi","notosanskhudawadi/v21/fdNi9t6ZsWBZ2k5ltHN73zZ5hc8HANlHIjRnVVXz9MY.ttf",!0),A.aY("Noto Sans Lao","notosanslao/v30/bx6lNx2Ol_ixgdYWLm9BwxM3NW6BOkuf763Clj73CiQ_J1Djx9pidOt4ccbdf5MK3riB2w.ttf",!0),A.aY("Noto Sans Lepcha","notosanslepcha/v19/0QI7MWlB_JWgA166SKhu05TekNS32AJstqBXgd4.ttf",!0),A.aY("Noto Sans Limbu","notosanslimbu/v22/3JnlSDv90Gmq2mrzckOBBRRoNJVj0MF3OHRDnA.ttf",!0),A.aY("Noto Sans Linear A","notosanslineara/v18/oPWS_l16kP4jCuhpgEGmwJOiA18FZj22zmHQAGQicw.ttf",!0),A.aY("Noto Sans Linear B","notosanslinearb/v17/HhyJU4wt9vSgfHoORYOiXOckKNB737IV3BkFTq4EPw.ttf",!0),A.aY("Noto Sans Lisu","notosanslisu/v25/uk-3EGO3o6EruUbnwovcYhz6kh57_nqbcTdjJnHP2Vwt29IlxkVdig.ttf",!0),A.aY("Noto Sans Lycian","notosanslycian/v15/QldVNSNMqAsHtsJ7UmqxBQA9r8wA5_naCJwn00E.ttf",!0),A.aY("Noto Sans Lydian","notosanslydian/v17/c4m71mVzGN7s8FmIukZJ1v4ZlcPReUPXMoIjEQI.ttf",!0),A.aY("Noto Sans Mahajani","notosansmahajani/v19/-F6sfiVqLzI2JPCgQBnw60Agp0JrvD5Fh8ARHNh4zg.ttf",!0),A.aY("Noto Sans Malayalam","notosansmalayalam/v26/sJoi3K5XjsSdcnzn071rL37lpAOsUThnDZIfPdbeSNzVakglNM-Qw8EaeB8Nss-_RuD9BFzEr6HxEA.ttf",!0),A.aY("Noto Sans Mandaic","notosansmandaic/v16/cIfnMbdWt1w_HgCcilqhKQBo_OsMI5_A_gMk0izH.ttf",!0),A.aY("Noto Sans Manichaean","notosansmanichaean/v17/taiVGntiC4--qtsfi4Jp9-_GkPZZCcrfekqCNTtFCtdX.ttf",!0),A.aY("Noto Sans Marchen","notosansmarchen/v19/aFTO7OZ_Y282EP-WyG6QTOX_C8WZMHhPk652ZaHk.ttf",!0),A.aY("Noto Sans Masaram Gondi","notosansmasaramgondi/v17/6xK_dThFKcWIu4bpRBjRYRV7KZCbUq6n_1kPnuGe7RI9WSWX.ttf",!0),A.aY("Noto Sans Math","notosansmath/v15/7Aump_cpkSecTWaHRlH2hyV5UHkG-V048PW0.ttf",!0),A.aY("Noto Sans Mayan Numerals","notosansmayannumerals/v16/PlIuFk25O6RzLfvNNVSivR09_KqYMwvvDKYjfIiE68oo6eepYQ.ttf",!0),A.aY("Noto Sans Medefaidrin","notosansmedefaidrin/v23/WwkzxOq6Dk-wranENynkfeVsNbRZtbOIdLb1exeM4ZeuabBfmErWlT318e5A3rw.ttf",!0),A.aY("Noto Sans Meetei Mayek","notosansmeeteimayek/v15/HTxAL3QyKieByqY9eZPFweO0be7M21uSphSdhqILnmrRfJ8t_1TJ_vTW5PgeFYVa.ttf",!0),A.aY("Noto Sans Meroitic","notosansmeroitic/v17/IFS5HfRJndhE3P4b5jnZ3ITPvC6i00UDgDhTiKY9KQ.ttf",!0),A.aY("Noto Sans Miao","notosansmiao/v17/Dxxz8jmXMW75w3OmoDXVV4zyZUjgUYVslLhx.ttf",!0),A.aY("Noto Sans Modi","notosansmodi/v23/pe03MIySN5pO62Z5YkFyT7jeav5qWVAgVol-.ttf",!0),A.aY("Noto Sans Mongolian","notosansmongolian/v17/VdGCAYADGIwE0EopZx8xQfHlgEAMsrToxLsg6-av1x0.ttf",!0),A.aY("Noto Sans Mro","notosansmro/v18/qWcsB6--pZv9TqnUQMhe9b39WDzRtjkho4M.ttf",!0),A.aY("Noto Sans Multani","notosansmultani/v20/9Bty3ClF38_RfOpe1gCaZ8p30BOFO1A0pfCs5Kos.ttf",!0),A.aY("Noto Sans Myanmar","notosansmyanmar/v20/AlZq_y1ZtY3ymOryg38hOCSdOnFq0En23OU4o1AC.ttf",!0),A.aY("Noto Sans NKo","notosansnko/v6/esDX31ZdNv-KYGGJpKGk2_RpMpCMHMLBrdA.ttf",!0),A.aY("Noto Sans Nabataean","notosansnabataean/v16/IFS4HfVJndhE3P4b5jnZ34DfsjO330dNoBJ9hK8kMK4.ttf",!0),A.aY("Noto Sans New Tai Lue","notosansnewtailue/v22/H4cKBW-Pl9DZ0Xe_nHUapt7PovLXAhAnY7wqaLy-OJgU3p_pdeXAYUbghFPKzeY.ttf",!0),A.aY("Noto Sans Newa","notosansnewa/v16/7r3fqXp6utEsO9pI4f8ok8sWg8n_qN4R5lNU.ttf",!0),A.aY("Noto Sans Nushu","notosansnushu/v19/rnCw-xRQ3B7652emAbAe_Ai1IYaFWFAMArZKqQ.ttf",!0),A.aY("Noto Sans Ogham","notosansogham/v17/kmKlZqk1GBDGN0mY6k5lmEmww4hrt5laQxcoCA.ttf",!0),A.aY("Noto Sans Ol Chiki","notosansolchiki/v29/N0b92TJNOPt-eHmFZCdQbrL32r-4CvhzDzRwlxOQYuVALWk267I6gVrz5gQ.ttf",!0),A.aY("Noto Sans Old Hungarian","notosansoldhungarian/v18/E213_cD6hP3GwCJPEUssHEM0KqLaHJXg2PiIgRfjbg5nCYXt.ttf",!0),A.aY("Noto Sans Old Italic","notosansolditalic/v16/TuGOUUFzXI5FBtUq5a8bh68BJxxEVam7tWlRdRhtCC4d.ttf",!0),A.aY("Noto Sans Old North Arabian","notosansoldnortharabian/v16/esDF30BdNv-KYGGJpKGk2tNiMt7Jar6olZDyNdr81zBQmUo_xw4ABw.ttf",!0),A.aY("Noto Sans Old Permic","notosansoldpermic/v17/snf1s1q1-dF8pli1TesqcbUY4Mr-ElrwKLdXgv_dKYB5.ttf",!0),A.aY("Noto Sans Old Persian","notosansoldpersian/v16/wEOjEAbNnc5caQTFG18FHrZr9Bp6-8CmIJ_tqOlQfx9CjA.ttf",!0),A.aY("Noto Sans Old Sogdian","notosansoldsogdian/v16/3JnjSCH90Gmq2mrzckOBBhFhdrMst48aURt7neIqM-9uyg.ttf",!0),A.aY("Noto Sans Old South Arabian","notosansoldsoutharabian/v16/3qT5oiOhnSyU8TNFIdhZTice3hB_HWKsEnF--0XCHiKx1OtDT9HwTA.ttf",!0),A.aY("Noto Sans Old Turkic","notosansoldturkic/v17/yMJNMJVya43H0SUF_WmcGEQVqoEMKDKbsE2RjEw-Vyws.ttf",!0),A.aY("Noto Sans Oriya","notosansoriya/v27/AYCppXfzfccDCstK_hrjDyADv5e9748vhj3CJBLHIARtgD6TJQS0dJT5Ivj0f6_c6LhHBRe-.ttf",!0),A.aY("Noto Sans Osage","notosansosage/v18/oPWX_kB6kP4jCuhpgEGmw4mtAVtXRlaSxkrMCQ.ttf",!0),A.aY("Noto Sans Osmanya","notosansosmanya/v18/8vIS7xs32H97qzQKnzfeWzUyUpOJmz6kR47NCV5Z.ttf",!0),A.aY("Noto Sans Pahawh Hmong","notosanspahawhhmong/v18/bWtp7e_KfBziStx7lIzKKaMUOBEA3UPQDW7krzc_c48aMpM.ttf",!0),A.aY("Noto Sans Palmyrene","notosanspalmyrene/v16/ZgNPjOdKPa7CHqq0h37c_ASCWvH93SFCPnK5ZpdNtcA.ttf",!0),A.aY("Noto Sans Pau Cin Hau","notosanspaucinhau/v20/x3d-cl3IZKmUqiMg_9wBLLtzl22EayN7ehIdjEWqKMxsKw.ttf",!0),A.aY("Noto Sans Phags Pa","notosansphagspa/v15/pxiZyoo6v8ZYyWh5WuPeJzMkd4SrGChkqkSsrvNXiA.ttf",!0),A.aY("Noto Sans Phoenician","notosansphoenician/v17/jizFRF9Ksm4Bt9PvcTaEkIHiTVtxmFtS5X7Jot-p5561.ttf",!0),A.aY("Noto Sans Psalter Pahlavi","notosanspsalterpahlavi/v16/rP2Vp3K65FkAtHfwd-eISGznYihzggmsicPfud3w1G3KsUQBct4.ttf",!0),A.aY("Noto Sans Rejang","notosansrejang/v21/Ktk2AKuMeZjqPnXgyqrib7DIogqwN4O3WYZB_sU.ttf",!0),A.aY("Noto Sans Runic","notosansrunic/v17/H4c_BXWPl9DZ0Xe_nHUaus7W68WWaxpvHtgIYg.ttf",!0),A.aY("Noto Sans SC","notosanssc/v36/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYxNbPzS5HE.ttf",!0),A.aY("Noto Sans Saurashtra","notosanssaurashtra/v23/ea8GacQ0Wfz_XKWXe6OtoA8w8zvmYwTef9ndjhPTSIx9.ttf",!0),A.aY("Noto Sans Sharada","notosanssharada/v16/gok0H7rwAEdtF9N8-mdTGALG6p0kwoXLPOwr4H8a.ttf",!0),A.aY("Noto Sans Shavian","notosansshavian/v17/CHy5V_HZE0jxJBQlqAeCKjJvQBNF4EFQSplv2Cwg.ttf",!0),A.aY("Noto Sans Siddham","notosanssiddham/v20/OZpZg-FwqiNLe9PELUikxTWDoCCeGqndk3Ic92ZH.ttf",!0),A.aY("Noto Sans Sinhala","notosanssinhala/v26/yMJ2MJBya43H0SUF_WmcBEEf4rQVO2P524V5N_MxQzQtb-tf5dJbC30Fu9zUwg2a5lgLpJwbQRM.ttf",!0),A.aY("Noto Sans Sogdian","notosanssogdian/v16/taiQGn5iC4--qtsfi4Jp6eHPnfxQBo--Pm6KHidM.ttf",!0),A.aY("Noto Sans Sora Sompeng","notosanssorasompeng/v24/PlIRFkO5O6RzLfvNNVSioxM2_OTrEhPyDLolKvCsHzCxWuGkYHR818DpZXJQd4Mu.ttf",!0),A.aY("Noto Sans Soyombo","notosanssoyombo/v17/RWmSoL-Y6-8q5LTtXs6MF6q7xsxgY0FrIFOcK25W.ttf",!0),A.aY("Noto Sans Sundanese","notosanssundanese/v24/FwZw7_84xUkosG2xJo2gm7nFwSLQkdymq2mkz3Gz1_b6ctxpNNHCizv7fQES.ttf",!0),A.aY("Noto Sans Syloti Nagri","notosanssylotinagri/v20/uU9eCAQZ75uhfF9UoWDRiY3q7Sf_VFV3m4dGFVfxN87gsj0.ttf",!0),A.aY("Noto Sans Syriac","notosanssyriac/v16/Ktk7AKuMeZjqPnXgyqribqzQqgW0LYiVqV7dXcP0C-VD9MaJyZfUL_FC.ttf",!0),A.aY("Noto Sans TC","notosanstc/v35/-nFuOG829Oofr2wohFbTp9ifNAn722rq0MXz76Cy_CpOtma3uNQ.ttf",!0),A.aY("Noto Sans Tagalog","notosanstagalog/v22/J7aFnoNzCnFcV9ZI-sUYuvote1R0wwEAA8jHexnL.ttf",!0),A.aY("Noto Sans Tagbanwa","notosanstagbanwa/v18/Y4GWYbB8VTEp4t3MKJSMmQdIKjRtt_nZRjQEaYpGoQ.ttf",!0),A.aY("Noto Sans Tai Le","notosanstaile/v17/vEFK2-VODB8RrNDvZSUmVxEATwR58tK1W77HtMo.ttf",!0),A.aY("Noto Sans Tai Tham","notosanstaitham/v20/kJEbBv0U4hgtwxDUw2x9q7tbjLIfbPGHBoaVSAZ3MdLJBCUbPgquyaRGKMw.ttf",!0),A.aY("Noto Sans Tai Viet","notosanstaiviet/v19/8QIUdj3HhN_lv4jf9vsE-9GMOLsaSPZr644fWsRO9w.ttf",!0),A.aY("Noto Sans Takri","notosanstakri/v23/TuGJUVpzXI5FBtUq5a8bnKIOdTwQNO_W3khJXg.ttf",!0),A.aY("Noto Sans Tamil","notosanstamil/v27/ieVc2YdFI3GCY6SyQy1KfStzYKZgzN1z4LKDbeZce-0429tBManUktuex7vGo70RqKDt_EvT.ttf",!0),A.aY("Noto Sans Tamil Supplement","notosanstamilsupplement/v21/DdTz78kEtnooLS5rXF1DaruiCd_bFp_Ph4sGcn7ax_vsAeMkeq1x.ttf",!0),A.aY("Noto Sans Telugu","notosanstelugu/v25/0FlxVOGZlE2Rrtr-HmgkMWJNjJ5_RyT8o8c7fHkeg-esVC5dzHkHIJQqrEntezbqQUbf-3v37w.ttf",!0),A.aY("Noto Sans Thaana","notosansthaana/v23/C8c14dM-vnz-s-3jaEsxlxHkBH-WZOETXfoQrfQ9Y4XrbhLhnu4-tbNu.ttf",!0),A.aY("Noto Sans Thai","notosansthai/v20/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzF-QRvzzXg.ttf",!0),A.aY("Noto Sans Tifinagh","notosanstifinagh/v20/I_uzMoCduATTei9eI8dawkHIwvmhCvbn6rnEcXfs4Q.ttf",!0),A.aY("Noto Sans Tirhuta","notosanstirhuta/v16/t5t6IQYRNJ6TWjahPR6X-M-apUyby7uGUBsTrn5P.ttf",!0),A.aY("Noto Sans Ugaritic","notosansugaritic/v16/3qTwoiqhnSyU8TNFIdhZVCwbjCpkAXXkMhoIkiazfg.ttf",!0),A.aY("Noto Sans Vai","notosansvai/v17/NaPecZTSBuhTirw6IaFn_UrURMTsDIRSfr0.ttf",!0),A.aY("Noto Sans Wancho","notosanswancho/v17/zrf-0GXXyfn6Fs0lH9P4cUubP0GBqAPopiRfKp8.ttf",!0),A.aY("Noto Sans Warang Citi","notosanswarangciti/v17/EYqtmb9SzL1YtsZSScyKDXIeOv3w-zgsNvKRpeVCCXzdgA.ttf",!0),A.aY("Noto Sans Yi","notosansyi/v19/sJoD3LFXjsSdcnzn071rO3apxVDJNVgSNg.ttf",!0),A.aY("Noto Sans Zanabazar Square","notosanszanabazarsquare/v19/Cn-jJsuGWQxOjaGwMQ6fOicyxLBEMRfDtkzl4uagQtJxOCEgN0Gc.ttf",!0)],t.Qg))}return r},'
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进行代理的相关配置。下载完成后的结果如下:85680b2eb9b00378ad51af98675166ca.jpeg

我们再将所有的字体资源上传到CDN上,然后修改main.dart.js文件,将Host改为我们CDN的相应地址,然后我们来看一下优化效果:b6663eec14f13f488a80b336d620cdf4.jpeg

优化之前也是2,3秒的样子,再看一下优化后的:badfea2333eff7ef69bf897d425d950b.jpeg也是毫秒级别,可见优化效果之明显。

字体的打包部署时如何优化我们已经了解了。那么我们开发调试的时候应该怎么处理呢?我们复习一下Flutter Web的渲染模式:4e5a046872f7fa3d30b9a7da144318d4.jpeg

Flutter为了实现多浏览器的效果一致性,在canvaskit渲染模式下,会下载相应的字体进行绘制。因此在开发调试的阶段,我们可以关闭canvaskit渲染模式使用html方式进行渲染,就不会加载这些字体了。命令如下:

flutter run -d Chrome  -t lib/main_test.dart --web-renderer html

这样就解决了开发调试时的字体问题。

05

总结

到此为止,我们通过main.dart.js分包,配置canvaskit.jscanvaskit.wasm的加载url,字体加载优化这三个方式可以为Flutter Web项目减少5-6秒的启动耗时。希望可以给大家一些帮助。

;