我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章。
关联文章
~ 开源开源 ~
对于 JS 如何与 Flutter 结合使用,笔者抽空整理了一个 MVP 版本及完全源码放出。
先附 git :github.com/yuanxiao-mm…
先放这一篇是因为算是最近在做,项目耦合性还比较小,把示例抽出来比较容易 ...
前置准备
(略)Flutter 环境安装
(略)node 环境安装
笔者是 mac 设备,所以 shell 脚本可以直接运行,如果是 windows ,可能需要创建一个批处理文件来执行脚本
运行示例
执行 ts_dart_gen 脚本
tools/ts_dart_gen.sh
首次或者 js_plugins 文件夹中代码发生变动即需执行(后续会分析脚本作用)。
启动 Flutter 项目
cd flutter_pages
flutter run -d chrome
这 示例 已 OK ~ 各位看官自己点点即可。
代码解析
实现上是比较简单的,但也基本覆盖了主要的使用方式和场景,这样就达到由前几篇文章所讲的,JS 负责业务层,Flutter 负责渲染的基本框架体系(见: 《Flutter Web - 让 Web 与 App UI 一致的另一种可能》)。
下面会分析各处代码的作用:
tools/ts_to_dart_facade
先从工具链讲起,前面文章也有写到 ts_to_dart_facade 脱胎于 js_facade_gen,用于解决 js_facade_gen 失修已久语法不支持(特别是空安全)和一些 TS 与 Flutter 对象转换的问题。
ts_to_dart_facade 也不详细介绍,全部代码已在 git 里,笔者改造的也不算好。说实话这 js_facade_gen 大佬写的比较绕,笔者认为实现上应该可以更简单粗暴点,毕竟原理很简单就是 Typescript AST + 字符串操作生成 Dart 代码的过程。
tools/ts_dart_gen.sh
这里笔者补充了一个 tools/ts_dart_gen.sh 执行脚本,来简化整个流程。
# 下载运行环境
cd $DIR/tools/ts_to_dart_facade
if [ ! -d "$TOOLDIR/node_modules" ]; then
npm install
fi
# 重新生成工具
npm run prepare
# 执行 ts_to_dart_facade
rm -r -- "$FLUTTERDIR/lib/api"*
./index.js --destination=$FLUTTERDIR/lib/api --base-path=$JSDIR/src/api $JSDIR/src/api/api.ts $JSDIR/src/api/plugins/*.ts $JSDIR/src/api/plugins/**/*.ts $JSDIR/src/api/api.ts $JSDIR/src/api/modules/*.ts $JSDIR/src/api/modules/**/*.ts
# 执行 json_annotation
cd $FLUTTERDIR
flutter pub get
flutter packages pub run build_runner build
# 执行 npm run build
cd $JSDIR
if [ ! -d "$JSDIR/node_modules" ]; then
npm install
fi
npm run build:debug
脚本执行分为这么几个部分:
- 检查 tools/ts_to_dart_facade 的运行环境,如果是首次使用,需要
npm intall - 执行
npm run prepare将 tools/ts_to_dart_facade 工具构建(如果工具不需要更改,也可以直接用产物即可,省去1,2步)。 - 清理 Flutter 项目中自动生成的 dart 代码。
- 执行工具
ts_to_dart_facade/index.js生成 dart 代码到 Flutter 项目中,--destination参数是生成到哪里,·--base-path是读取哪一些 TS 文件,这个写法也需要注意,毕竟是使用的 AST 抽象语法树做的,所以有依赖关系的都要包含进去,这也是为什么会定义接口层的原因。 - 在 Flutter 工程中执行
flutter packages pub run build_runner build用于序列化,自身集成了 json_annotation 。 - 后面就是将
js_plugins项目构建后输出到 Flutter 项目/web中,然后由 Flutter 运行或者构建最终项目。
脚本最后也有一个细节:
npm run build:debug,可以看到笔者把环境变量控制定义在js_plugins侧,这样实现层完全是由JS控制,Flutter只关心渲染。如果要增加React / VueWeb 渲染,那js_plugins也是完全可复用的。
js_plugins
再看看 js_plugins 业务层是如何做的。
里面也很简单,主要就是 /api 接口层设计。
接口层设计上面也有说到,主要是为了 TS 的抽象语法树获取是有范围的,不能也不应该把整个 TS 代码都转换成 dart。所以我们尽量用 interface 来定义需要暴露给 flutter 侧使用的内容。
/impl 就是 /api 的实现层,实现具体的接口逻辑。
这里需要着重讲一下 @DartObject() 这个注解(这里用 Java 叫法,标准上叫装饰器可以,看到笔者在 base.ts 文件中定义了一个空的类装饰器:
export function DartObject(): ClassDecorator {
return function (_: Function) {} as ClassDecorator
}
它使用上是注解了一个 class
@DartObject()
export class GDHomeSearchKeyResponse {
list?: String[]
}
功能是给 ts_to_dart_facade 工具解析用的,是用于标注该 ts class 需要被转换成 Dart class,需要生成额外的序列化对象(也就是用到 json_annotation),在 Typescript 代码里没并有实际意义。
小常识:Typescript 注解能力需要在
tsconfig.json中增加"emitDecoratorMetadata": true
main.ts 主要就是把各项能力挂载到 window 上,可以让 js/js.dart 调用得到。当然在实际项目里还负责各个能力的 init。
flutter_pages
这个是我们的 Flutter 项目了,可以看到 lib/api 里已经有了各个用于链接 ts 的 dart 代码
直接引用 api/api.dart 即可直接使用。
有个小细节, /web 和 /lib/api 都设置了 .gitinore, 一般自动生成的代码都不需要提交 git,减少 codereview。
/modules/home/home.dart 中就是调用示例,比如
GDPlugin.location.open('https://www.gaoding.com'); 就是调用 JS 的 window.location.open(),功能都是由 JS 去实现即可。
有一点要说明,JS 中的 promise 会转换成 Dart 中的 Future,所以即支持同步操作,也支持异步操作。
这里还要说一个细节,如果想变更实现或者增加其他功能, 在 js_plugins 修改完成,执行 tools/ts_dart_gen.sh 脚本后,还要重新 flutter run -d chrome 来重新加载 /web 中的 index.html。
总结
大体就这些,其实除了改造 js_facade_gen 比较费时,其他实现上还是水到渠成的。当然对于技术栈属于“前端”或者“Flutter App”的同学可能只能看懂一半,但走了技术这一条路,不就是卷来卷去[狗头]。
其实这套方案除了为了降本增效,UI 一致性等目标外,还有一些技术目标。
在技术趋于融合的现状下,前端会越来越重要,有篇掘友的文章也写到过,他认为后面前端开发会分为 “Web UI” + “Web Core”。 那这套方案其实就是 “Web Core” + “Flutter UI” 的实现,完全可以做到 “前端 | Flutter” 开发人员解耦,当然 “Web Core” 更适合被 “Rust Core” 代替,但 wasm 包体积太大了,不适用 Web 场景。
后续
如果示例跑不起来,可以评论区留言,毕竟笔者也只是在自己设备跑了一下~,难免有疏漏的地方。
如果对你开发学习有丝丝作用,请点个赞,谢谢。[开心]