专家实战解读 Flutter for Web
web 下的 index.html:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注销账户</title>
<script defer src="main.dart.js" type="application/javascript"></script>
</head>
<body>
</body>
</html>
这里就更简单了,这里就是一个简单的 html 文件,这个 html 引入一个叫 main.dart.js 的文件,你会发现新建的项目中是没有这个 js 文件的,那么这个文件是从哪里来的呢?这里其实是项目自动生成的一个 js 文件。我们后面会分析下这里。我们不建议在这个 html 文件中创建任何 element、css 样式等,但是你可以在这里面加载 js 文件,如果你需要在 dart 中调用 js 文件的方法,请记得加载顺序。
通过上面的分析,你应该知道,这里的开发其实比较简单,如果你完全不需要任何 js 的引入,那么你直接可以在 lib 下面开发你的功能就可以,如果你需要引入一些第三方 js 库或者实现其他的一些 js 方法,那么你可以在 web 目录下面开发,在 index.html 中引入,如果你想自己在 index.html 中嵌入 element、css 样式的话,你会发现并不行,但是如果你想嵌入 html 页面的话,后面我们会教你如何去做。
如何构建
开发完成后我们需要将生产的文件发布,那需要发布哪些文件呢?我们来对 Flutter For Web 的构建和产物来做下分析。上面的调试在启动后,其实是会在.dart_tool 文件夹下生成一个 build 文件夹,这个文件夹中都是生成的中间产物。
而由于需要支持 hot reload,所以在 debug 模式下会生成多个 JS 文件,这点我们可以在一个页面加载的资源中看到:
可以看到,这个页面加载了很多 js、html 等文件。
那我们如果需要发布的时候,是不是也需要这么多文件呢,如果这样的话,将会对发布造成很大负担,因为要发很多文件。根据官方的指引,我们是用“flutter build web”命令生成发布产物看下。
执行这个命令后,会在项目的 build 目录下生成一个 web 文件夹,而不是在 dart_tool 文件夹下生成的。这个文件夹下生成的内容如下:
这个 web 目录下有一个文件夹、三个文件,我们来分析下这下面的几个文件夹和文件。
assets:资源文件夹,打开后发现这里存的是 web 目录下的资源文件,那说明这个是必须要发布的。这里面有自动帮你生成了 AssetManifest.json 和 FontManifest.json 两个文件,这两个文件在线上访问是必须会加载的,目前没有发现有屏蔽方法,比较建议加上,否则会出现 404 错误,如果在部分服务器上可能会引发跳转,可能会导致出现异常。
index.html:这里和 web 目录下的 index.html 一样,是整个 web app 的访问入口,必须要发布。
main.dart.js:
这是通过 dart2js 将使用 dart 写的 flutter 代码生成了 js 便于加载,你所写的 flutter ui 样式、dart 业务逻辑等都将转成 js 输出到这个文件中,然后被 index.html 加载,最后渲染呈现在 webview 上。所以这里就是为什么开始时候说不需要在 index.html 中写 css 样式,因为在这里是不会被识别的。main.dart.js.map:这是一个 js 的 map 文件,不需要发布,主要用于出错时候方便定位使用。
之后我们将这里的所有文件复制到我们的 web 服务器下就可以了。
Package & Plugin
Flutter 团队一直声称 Flutter For Web 暂时不支持插件系统,这个从 1.5 到 1.9 都没有支持。但是这个只是说不支持特定平台的 Plugin 调用,并不意味着你无法使用 yaml 配置一些纯 dart 的 package,所以,你还是可以在 yaml 里面引入外部的 package 使用的,如 fish-redux 等都是可以支持的。最近 dio 也支持了 web 版本,是可以直接引用使用的。
如何开发
说了这么多,我们聊一聊如何在开发 Flutter For Web 中的注意事项。Flutter For Web 开发本质上和 Flutter For Mobile 开发无任何区别,所有的逻辑都可以用 Dart 实现,你可以使用任何控件,甚至可以使用各种状态管理等,这里 Flutter 官方都有比较详细的教程,我们这里就不需要详细说明了,可以参考官网教程。但是目前由于生态不太完善的原因,我们又无可避免的需要引用到第三方,或者一些特殊的 Web 相关逻辑,那么 dart 和 js 的交互就必不可少了。我们期待最优秀的交互方式肯定还是 Plugin 的方式,但是由于不支持,所以如果现在有需要的话 还是需要用代码和 JS 交互。
HTML
其实 Flutter For Web 最终还是编译成了 html 和 js 来运行,那么我们就需要一些 html 相关的操作,如 document、ua、cookie 等等。而在 Flutter 里,是有专门的“dart:html”库的,你可以 import 后使用。
import 'dart:html';
/**
* @description: 跳转某个 weburl
* @param {url}
*/
navigation(String url){
document.window.location.href = url;
}
import 'dart:html';
Map _readCookie(String cookies) {
var cookies = document.cookie;
var cookie = Map();
cookies.split(';').forEach((e) {
var k = e.indexOf('=');
if (k > 0) {
cookie[Uri.decodeComponent(e.substring(0, k)).trim()] =
Uri.decodeComponent(e.substring(k + 1)).trim();
}
});
return cookie;
}
JavaScript
Flutter For Web 的目的其实是用完全 dart 来开发 web 应用,但是目前市场很多第三方 web 插件,如统计等,还是没有 dart 版本的,那么我们就需要用 datr 和 js 互相调用的方式。而 Flutter 也提供了“dart:js”库可以导入后和 JS 进行交互。
JS 的交互里面有个很重要的概念是 context,我们需要拿到这个 context 上下文来和 js 交互。可以给 js 方法传参,甚至还可以将 js 的参数带回等等。比如以下代码:
import 'dart:js' as js;
/**
* @description: 从 dart 里面 直接调用 js 方法
* @param {method} 方法名
* @param {[List args]} 可选参数
* @return:
*/
calljsmethod(String method,[List args]){
js.context.callMethod(method,args);
}
/**
* @description: 从 dart 里面 直接调用 js 方法 并且传递一个 callback 过去 以便于回调
* @param {method} 方法名
* @param {callback} 回调函数
* @param {[List args]} 可选参数
* @return:
*/
calljswithcallback(String method,Function callback,[List args]){
js.context.callMethod(method,[js.allowInterop(callback),args]);
}
/**
* @description: 将 dart 方法设置成 js 方法 以供调用
* @param {functioname} 设置的函数名称 js 那边调用
* @param {callback} 设置的函数
* @return:
*/
setjsmethod(String functioname,Function callback){
print("setjsmethod ${functioname}");
js.context[functioname] = callback;
}
网络相关
网络请求从来都是开发一个 App 的重点功能,而 Flutter 开发中用的最广泛的 dio 插件,目前在最新版本是已经可以支持 web 的。
我们可以直接用 dio 进行网络请求,也可以通过“package:http/http.dart”库来进行网络请求。
doRequest(String url, Function success, {body, Function error}) {
if (!isEmpty(body)) {
url += "?data=${Uri.encodeFull(body)}";
}
print("url is $url");
http.get(url).then((resp) {
print('Response status: ${resp.statusCode} Response body: ${resp.body}');
String bodyString = resp.body;
Map data = JsonDecoder().convert(bodyString);
print('data is $data');
if (resp.statusCode == 200) {
success(resp.statusCode, data);
} else {
if (error != null) {
error(resp.statusCode, resp.body);
} else {
print('error is null not callback');
}
}
}).catchError(( ) {
print("catch some error ${ }");
if (error != null) {
error(-10001, "");
}
}).timeout((Duration(seconds: 15)));
}
这里我没有用 Completer 来实现,而是直接用的比较简单的回调,也可以封装一下。
性 能
性能方面才是衡量一个技术的最重要的指标,当前阶段的 Flutter For Web 还只是技术预览版本,官方也承认性能是有问题的,Flutter For Web 目前也是单页面应用,但是浏览器还不支持其而其渲染方式采用的是 Canvas 加原生 Dom 的方式来渲染的,这其实就会带来一些性能问题,比如 main.dart.js 轻松过 1M,导致首次加载时长可能过慢,比如 canvas 绘制可能会导致性能问题。
我们以下面这一项目为例:
https://github.com/arcticfox1919/flutter_new_retail
这个项目本来是一个 Flutter For Mobile 的电商项目,原作者也未对 web 进行任何兼容。但是我们可以直接编译成 web,并且跑起来,这也侧面说明了 Flutter 的强大之处,一份代码多端运行。
由于这个项目页面算是比较复杂,并且有带图的列表,我们用这个项目来测试下 Flutter 列表的性能,有兴趣的也可以自己试试。
问 题
最大的问题当然是 Flutter For Web 至今未 Release,而且官方团队并没有任何 Release 计划和时间点,这就导致 Flutter For Web 的未来充满了不可确定性。其次就是性能问题,目前 Flutter For Web 这种形式确实会带来不少的性能问题,但是 1.9 版本已经比之前的版本性能好很多了。我们期待官方的最终优化。最后就是生态问题,Flutter For Web 方面目前的生态还不完整,希望官方能尽快推出 Release 版本,然后希望社区可以完善下整个生态。
后 记
一份代码多端运行,在 Flutter 这里我们真正看到了可能,终于看到大前端技术的曙光了。
作者介绍
王威威,腾讯高级工程师,多年移动端开发经验,目前技术栈在大前端,对新技术很感兴趣,对 Flutter 相关技术栈以及 Web 方向有较多研究。
今日推荐