共计 4411 个字符,预计需要花费 12 分钟才能阅读完成。
Flutter Web 作为 Flutter 框架中最非凡的平台,因为 Web 平台的特殊性,它默认就具备了两种不同的渲染引擎:
html:通过平台的 canvas 和 Element 实现布局绘制;
canvaskit:通过 Webassembly + Skia 绘制控件;
尽管都晓得 canvavskit 更靠近 Flutter 的设计理念,然而因为它构建的 wasm 文件大小和字体加载等问题带来的老本思考,业界个别会选用更轻量化的 html 引擎,而明天的问题也是基于 html 引擎来开展。
一、deferred-components
咱们都晓得 Flutter Web 打包构建后的 main.dart.js 文件会很大,所以 个别都会采纳一些办法来对包大小进行优化,而其中最罕用的形式之一就是应用 deferred-components 。
对于 deferred-components 官网起初次要是用于反对 Android App Bundle 上的动静公布,而通过适配后这项能力被很好地拓展到了 Web 上,通过 deferred-components 能够不便地依据需要来拆分 main.dart.js 文件的大小。
当然这里并不是介绍如何应用 deferred-components,而是在应用 deferred-components 时,遇到了一个对于 Flutter Web 在打包构建上的神奇问题。
首先,代码如下图所示,能够看到,这里次要是通过 deferred as 关键字将一个一般页面变成 deferred-components,而后在路由关上时通过 libraryFuture 加载后渲染页面。
这里省略了无关的 yaml 文件代码,那么上述简略的代码,大家感觉有没有什么问题?
一开始我也感觉没什么问题,通过 flutter run -d chrome –web-renderer html 运行到浏览器调试也没问题,页面都能够失常加载关上,然而当我通过 flutter build web –release –web-renderer html 打包部署到服务器后,关上时却遇到了这个问题:
Deferred library scroll_listener_demo_page was not loaded.
main.dart.js:16911 at Object.d (http://localhost:64553/main.d…)
main.dart.js:16911 at Object.aL (http://localhost:64553/main.d…)
main.dart.js:16911 at asV.$1 (http://localhost:64553/main.d…)
main.dart.js:16911 at pB.BE (http://localhost:64553/main.d…)
main.dart.js:16911 at akx.$1 (http://localhost:64553/main.d…)
main.dart.js:16911 at eT.t (http://localhost:64553/main.d…)
main.dart.js:16911 at Cw.bp (http://localhost:64553/main.d…)
main.dart.js:16911 at Cw.ih (http://localhost:64553/main.d…)
main.dart.js:16911 at Cw.rz (http://localhost:64553/main.d…)
main.dart.js:16911 at Cw.zk (http://localhost:64553/main.d…)
复制代码
这就很奇怪了,明明 debug 运行时没有问题,为什么 release 公布就会 not loaded 了?
通过简略调试和打印发现,在出错时代码时基本进入不到 ContainerAsyncRouterPage 这个容器里,也就是在内部就呈现了 not loaded 异样,然而明明 widget 是在 ContainerAsyncRouterPage 容器内才调用,为什么会在内部就抛出 not loaded 的异样?
通过异样信息比对源码发现,编译时在对于 deferred as 进行解决时,会插入一段 checkDeferredIsLoaded 的查看逻辑,所以抛出异样的代码是在编译期时解决 import * deferred as 时增加。
通过查看打包后的文件,能够看到如果在 checkDeferredIsLoaded 之前没有实现加载,也就是对应 importPrefix 没有被增加到 set 里,就会抛出异样。
所以初步推断,问题应该是呈现在 debug 和 release 时,对于 import * deferred as 的编译解决有不同之处。
二、构建区别
通过材料能够发现,Flutter Web 在不同编译期间会应用 dartdevc 和 dart2js 两个不同的编译器,而如下图所示,默认 debug 运行到 chrome 时采纳的是 dartdevc,因为 dartdevc 反对增量编译,所以能够很不便用 hot reload 来调试,通过这种形式运行的 Flutter Web 并不会在 build 目录下生成 web 目录,而是会在 build 目录下生成一个长期的 *.cache.dill.track.dill 用于加载和更新。
.dill 属于 Flutter 编译过程的两头文件,该文件个别是二进制的编码,如果想要查看它的内容,能够在完整版 dart-sdk 的 /Users/xxxxx/workspace/dart-sdk/pkg/vm/bin 目录下)执行 dart dump_kernel.dart xxx.dill output.dill.txt 查看,留神是完整版 dart-sdk。
而 Flutter Web 在 release 编译时,如下图所示,会通过 flutter_tools 的 web.dart 内的对应配置逻辑进行打包,应用的是 dart2js 的命令,打包后会在 build 下生成蕴含 main.dart.js 等产物的 web 目录,而打包过程中的产物,例如 app.dill 则是存在 .dart_tool/flutter_build/ 一串特地编码 / 目录下。
.dart_tool/flutter_build/ 目录下依据编译平台会输入不同的编译过程目录,点开能够看到是带 armeabi-v7a 之类的个别是 Android、带有 *.framework 的个别是 iOS,带有 main.dart.js 的个别是 Web。
而关上 web.dart 文件能够看到很多可配置参数,其中要害的比方:
–no-source-maps:是否须要生成 source-maps;
-O4:代表着优化等级,默认就是 -O4,dart2js 反对 O0-O4,其中 0 示意不做任何优化,4 示意优化开到最大;
–no-minify:示意是否混同压缩 js 代码,默认 build web –profile 就能够敞开混同;
所以到这里,我初步狐疑是不是优化等级 -O4 带来的问题,然而失常状况下,Flutter 打包时的 flutter_tools 并不是应用源码门路,而是应用以下两个文件:
/Users/xxxx/workspace/flutter/bin/cache/flutter_tools.stamp
/Users/xxxx/workspace/flutter/bin/cache/flutter_tools.snapshot
难道就为了改个参数就去编译整个 engine?这样必定是不值得的,所幸的是官网提供了应用源码 flutter_tools 编译的形式,同样是在我的项目目录下,通过一下形式就能够用 flutter_tools 源码的模式进行编译:
dart ~/workspace/flutter/packages/flutter_tools/bin/flutter_tools.dart build web –release –web-renderer html
而在源码里间接将 -O4 调整了 -O0 之后,我发现编译后的 web 竟然无奈失常运行,然而基于编译后的产物,我能够间接比对它们的差别,如下图所示,右边是 O0,左边是 O4:
-O0 之后为什么会无奈运行有谁晓得吗?
首先能够看到,O4 的确做了不少优化从而精简了它们的体积,然而在要害的 loadDeferredLibrary 局部根本一样,所以问题并不是呈现在这里。
然而到这里能够发现另外一个问题,因为 loadDeferredLibrary 办法是异步的,而从编译后的 js 代码上看,在执行完 loadDeferredLibrary 之后马上就进入到了 checkDeferredIsLoaded,这显然存在问题。
那为什么 debug 能够失常执行呢?通过查看 debug 运行时的 js 代码,我发现同样的执行逻辑,在 dartdevc 构建进去后竟然齐全不一样。
能够看到 checkDeferredIsLoaded 函数和对应的 Widget 是被一起放在逗号表达式里,所以从执行时序上会是和 Widget 在调用时被一起被执行,也就是在 loadDeferredLibrary 之后,所以代码能够失常运行。
通过断点调试也验证了这个时序问题,在 debug 下会先走完 loadDeferredLibrary 的全副逻辑,之后再进入 checkDeferredIsLoaded。
而在 release 模式下,代码尽管也会先进入 loadDeferredLibrary , 然而会在 checkDeferredIsLoaded 执行之后才进入到 add(0.this.loadId),从而导致后面的异样被抛出。
那到这里问题根本就很分明了,后面的代码写法在以后(2.10.3)的 Flutter Web 上,通过 dart2js 的 release 编译后会呈现某些时序不统一的问题,晓得了问题也很好解决,如下代码所示,只须要把原先代码里的 Widget 变成 WidgetBuilder 就能够了。
咱们再去看 release 编译后的 js 文件,能够看到此时的因为多了 WidgetBuilder,传入的内容变成了 closure69,这样就能够保障在调用到 call 之后才触发 checkDeferredIsLoaded。
三、最初
尽管这个问题不难解决,然而通过这个问题去理解 dart2js 的编译和构建过程,能够看到很多平时不会接触的内容
最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163 互相学习,咱们会有业余的技术答疑解惑
如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点 star:http://github.crmeb.net/u/defu 不胜感激!
残缺源码下载地址:https://market.cloud.tencent….
PHP 学习手册:https://doc.crmeb.com
技术交换论坛:https://q.crmeb.com