我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章。
关联文章
《Flutter Web - 让 Web 与 App UI 一致的另一种可能》
背景
用上文的方式,落地稿定 WAP 版的过程中,遇到了一个严重的卡点:
如何将 Flutter build web
的资源 CDN 化,也是笔者以前接触比较少的(笔者以前 Web 开发经验更多是管理后台以及离线包,很少需要接触到部署)。
为什么是卡点?在于 Flutter 默认仅支持相对域名的资源加载方式,无法使用当前域名以外的 CDN 域名,导致无法享受 CDN 带来的优势。
原以为 Flutter 官方有现成的方案,翻了一大圈,只能证明自己想的太美 ...
方案探寻
不过,在美团技术团队发表的 FlutterWeb性能优化探索与实践 中找到了部分解决方式:
- 对于图片相关的资源在
index.html
上增加 meta,可以解决 assets 资源路径是相对路径的问题问题。
<meta name="assetBase" content="你的 CDN 路径" />
- 上述 meta 对于加载的 JS 文件不适用(Flutter 官方快支持)。 main.dart.js 特别是用了延迟加载
deferred-components
会生成多个 main.dart.js_XX.part.js 多个 JS 的情况下,怎么配置 CDN 域名就成了一个大难题。
美团技术团队也输出了一种方案:
通过对
js_helper.dart
的动态编译,读取src
属性修改为读取assetBase
来实现xxx.part.js
文件的 CDN 加载
笔者看了下 js_helper.dart
代码
Emmm ... 3000 多行代码,而且还要准备 hook dart 的工具,或者自行编译 Flutter Engine,并不是一个短期能实现的一种方式。
那解决思路是 hook 来改变 <script src='xxx'>
,那是不是直接从 JS 代码 hook 就行了,毕竟 JS 这运行时可有无限可(bug)能(bug),改造也更简单。
失败的第一版
通过观察 flutter.js
文件以及研究 main.dart.js
,发现其实也都是动态添加 element 的方式添加 script 的,那我们直接 hook createElement
方法是不是就可以拿到 script 的创建对象,然后再去增加 CDN 的域名即可。
说做就做:
var _createElement = document.createElement.bind(document)
document.createElement = function (tagName: string) {
var element = _createElement(tagName)
if (tagName.toLowerCase() == 'script') {
Object.defineProperty(element, 'src', {
set: function (val) {
this.sourceSrc = val
return val
},
get: function () {
return this.sourceSrc
},
})
}
return element
}
通过重写 script src 的 get
set
方法,看起来是可行的,set
方法也是会成功拦截。但 get
方法根本不会执行,这可能是 script src 是特有属性,有固定的底层逻辑,不能被重写(没找到相关说明,有了解的同学评论一下)。
所以这并不可行,需要想其他的 hook 方式。
成功的第二版
直接 hook src
不可取的话,我们就要从它的代码上下游做手脚才行。
flutter.js
main.dart.js
通过 build
后的代码观察到,是通过 main.dart.js 的加载是通过 body.append
, main.dart.js_XX.part.js 的动态加载是通过 body.appendChild
。
那我们直接 hook 这两个方法是不是可以呢?
代码也很简单:
/**
* hook appendChild
*/
let appendChild = document.body.appendChild
document.body.appendChild = function (el) {
return appendChild.call(document.body, convertCDNScript(el))
}
/**
* hook append
*/
let append = document.body.append
document.body.append = function (el) {
append.call(document.body, convertCDNScript(el))
}
/**
* 转换成带有 CDN 域名的 Script
*/
function convertCDNScript(el) {
const cdnURL = import.meta.env.VITE_GAODING_CDN
if (cdnURL !== '/' && el.nodeName.toLowerCase() === 'script' && el.baseURI !== cdnURL) {
el.src = el.src.replace(el.baseURI, cdnURL)
}
return el
}
大功完成, Enjoy ~
后续
用 hook 的方式毕竟不是什么长(cou)久(he)之(de)计(yong),还是期待 Flutter 官方提供一个 API 或者环境变量设置。
本篇就只是抛砖引玉,期待能有同学放出来更优雅的实现方式。
说声抱歉, 还没有整理前面几篇文章的 codegen 源码,想要的同学可能还要耐心等下了 ... 最近工作太忙,这篇也就是随手记一下刚好遇到的 CDN 问题。
如果对你开发学习有点用处,请点个赞,谢谢。[开心]