Vite + Flutter:打造不一样的前端框架(附源码)

1,304 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章。

关联文章

《Flutter Web - 让 Web 与 App UI 一致的另一种可能》

《Flutter Web - 一种取巧的 CDN 方案》

《Flutter Web - 优雅的兼容 Flutter App 代码》

~ 开源开源 ~

对于 JS 如何与 Flutter 结合使用,笔者抽空整理了一个 MVP 版本及完全源码放出。

先附 git :github.com/yuanxiao-mm…

先放这一篇是因为算是最近在做,项目耦合性还比较小,把示例抽出来比较容易 ...

前置准备

(略)Flutter 环境安装

(略)node 环境安装

笔者是 mac 设备,所以 shell 脚本可以直接运行,如果是 windows ,可能需要创建一个批处理文件来执行脚本

运行示例

执行 ts_dart_gen 脚本

tools/ts_dart_gen.sh

首次或者 js_plugins 文件夹中代码发生变动即需执行(后续会分析脚本作用)。

image.png

启动 Flutter 项目

cd flutter_pages

flutter run -d chrome

image.png

这 示例 已 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

脚本执行分为这么几个部分:

  1. 检查 tools/ts_to_dart_facade 的运行环境,如果是首次使用,需要 npm intall
  2. 执行 npm run prepare 将 tools/ts_to_dart_facade 工具构建(如果工具不需要更改,也可以直接用产物即可,省去1,2步)。
  3. 清理 Flutter 项目中自动生成的 dart 代码。
  4. 执行工具 ts_to_dart_facade/index.js 生成 dart 代码到 Flutter 项目中,--destination 参数是生成到哪里,·--base-path 是读取哪一些 TS 文件,这个写法也需要注意,毕竟是使用的 AST 抽象语法树做的,所以有依赖关系的都要包含进去,这也是为什么会定义接口层的原因。
  5. 在 Flutter 工程中执行 flutter packages pub run build_runner build 用于序列化,自身集成了 json_annotation
  6. 后面就是将 js_plugins 项目构建后输出到 Flutter 项目 /web 中,然后由 Flutter 运行或者构建最终项目。

脚本最后也有一个细节:npm run build:debug,可以看到笔者把环境变量控制定义在 js_plugins 侧,这样实现层完全是由 JS 控制,Flutter 只关心渲染。如果要增加 React / Vue Web 渲染,那 js_plugins 也是完全可复用的。

js_plugins

再看看 js_plugins 业务层是如何做的。

image.png

里面也很简单,主要就是 /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 代码

image.png

直接引用 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 场景。

后续

如果示例跑不起来,可以评论区留言,毕竟笔者也只是在自己设备跑了一下~,难免有疏漏的地方。

如果对你开发学习有丝丝作用,请点个赞,谢谢。[开心]