敞开生长之旅!这是我参与「日新方案 12 月更文应战」的第15天,点击查看活动概况

前文传送门

《从零开始|构建 Flutter 多引擎烘托组件:先导篇》 《从零开始|构建 Flutter 多引擎烘托组件:Flutter 工程篇》 《从零开始|构建 Flutter 多引擎烘托组件:Flutter 代码篇》 《从零开始|构建 Flutter 多引擎烘托组件:Native 调用篇》

前些文章比较重视源码解说,所以堆了较多的代码来做具体说明,这行为有些吃力不讨好 – -|

本篇会用更少的代码以及更多的考虑来具体描述我们是怎样落地 Flutter 多引擎烘托组件开发东西链的。

引题

先想想我们构建跨端 UI 组件库的政策是什么?除了 UI 一致性烘托外,最重要的仍是降本增效啊。终究假设公司肯用“爆兵战术”不计本钱的话啥能做不出来?(说的是有用本钱,当然要扫除小马哥那种内部贪腐的比如[狗头])。要用有限的资源来做更大的“瑞士卷”,这即符合往后的发展趋势,也是“秃头”程序员一生所寻求的(啊 bushi)。

而在前文的开发流程中,写一个 Flutter 多引擎烘托组件真实真实是太费事了,虽然我们用了 pigeon 减少了一部分通讯代码,但剩余的代码流程也足以让一个开发变成退堂鼓选手。怎样处理开提问的问题,才是本系列的重中之重。

方案

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

先回想下笔者在先导篇中所说的 FGUIComponentAPI, 有看过之前几篇文章的同学应该会对它有所形象,但 FGUIComponentAPI 并不单指跨端组件库,也不单是今天所说的跨端东西链,它是全体方案的集合概念,包括配套环境、开发流程、成果产品。

再回到本文所要讲的跨端东西链,要处理的就是开发流程的问题。简化开发流程,前进开发领会。

《提到跨端东西链,我们是在说什么?》,对跨端东西链概念迷惑的同学可以看一下笔者这篇文章。

完结作用

先看一下现在的运用作用

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

在 VSCode 中实行 command + shift + p 选择 GaodingFlutter: run generate api 指令后,即会自动依据组件定义生成了相应的根底代码,包括前几篇文章内所讲的 api.dartbase.dart.h / .m.kt 等。

这儿还要其他说明一下,生成的文件我们选择不上传 git,优点有许多:减少无效 code review,防止提交抵触,生成成果改变对开发者来说无感知。而带来的代价是需求在组件库变化后,需求开发实行一下指令才华正常运转。当然我们在 VS Code Extension 中也如 flutter pub get 指令相同,做了更新提示。

《构建 VS Code Extension,前进 Flutter 开发功率(一)》 有喜好可以看看我们怎样是开发扩展的

下面就具体讲一下我们在其间做了什么以及是怎样做的。

建设要点

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

全体流程如上图所示,这就是指令实行的整个调用流程,看起来流程不算短,但只要理解了其间思想,整个流程其实是瓜熟蒂落的体力活。

定义而非 AST

我们为什么用 YAML 定义组件而不是类似 pigeon 用 Flutter 代码 code to code 的方法完结,最主要的原因是对开发者运用上进行限制及规范,防止不正确的。

我们在项目里增加了一个 schema.json 来供应说明及 Lint 能力。

ui_components.schema.json 部分示例

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "$id": "https://json.schemastore.org/ui_components",
  "description": "UI Components 定义规范",
  "items": {
    "type": "object",
    "description": "定义组件",
    "required": ["name", "options"],
    "properties": {
      "name": {
        "type": "string",
        "default": "XXX",
        "description": "组件称号",
        "examples": ["XXXView"]
      },
      "init": {
        "type": "array",
        "description": "组件初始化参数",
        ...
      }
    ...
  }
  ...
}

作用如下图,可以很好的限制传入参数的类型或许值,以及供应每一项的说明及方便生成运用模版。

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

具体定义规范,我们在之前的文章 《Flutter 多引擎烘托,在稿定 App 的实践(二):原理篇》 中有具体描述,这儿不再做赘述。

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

(定义文件放在组件包内,便于维护)

我们在脚本代码里,也是有定义相关的类,当然,直接用 map 也没什么问题,脚本言语考究没那么多,这儿方法上选的 Ruby,用 Python 或许 Node 也都可以,但不要用 dart 脚本,功率太低。

复制资源文件

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

如上图所示的 Android 部分,终究我们生成后是一个独立的插件包,所以必要的需求仿制一些固定资源,这样减少模版代码的作业量。

构造模版代码

什么是模版代码?直接贴图举例,仍是上图 Android 模版中,直接看预览:

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

其实模版就是把真实代码中需求变量的部分,用特定的字符串替代,例如 ${NAME},然后在脚本代码中做全局替换即可,是不是十分简略 ~

脚本代码里要做的事,一是类型转化,二是代码字符串拼装即可。

类型转化

我们在 YAML 用的是 Flutter 语法做 type 定义,这儿需求转成其他端言语的运用方法,这儿举例比较恶心的 OC 代码,就需求供应额定的东西方法来处理修饰符:

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

以及 * 指针的问题。

这一点除了官方有供应 AST 东西的(比如 Typescript),大部分三方库也都是类似的规划,只不过笔者这儿功用比较单一,所以处理方法就更简略粗暴些,有名的三方库会运用比如策略形式等规划形式让代码更优雅,但原理都相同,都是字符串的转化。

代码拼接

代码拼接就更简单理解了,读取模版 – 全局替换 – 写入文件算了,简略发一下代码:

dart_part_api.rb

module DartBaseGenerator
  ...
  def DartBaseGenerator.generator_api(model)
    # 写入途径
    filePath = File.join(@apiDicPath, model.name.underscore + ".dart")
    # 读取模版
    content = File.read("./resources/Flutter/api.dart.txt")
    # 全局替换
    content = content.gsub("${NAME}", model.name) ...
    file = File.new(filePath, "w")
    if file
      file.syswrite(content)
    end
    file.close
  end
end

优化脚本功率

抛开 pigeon 生成部分,脚本功率都是毫秒级的,终究仅仅是 O(n) 复杂度的替换进程以及 I/O 文件读写。但 pigeon 确实十分耗时,文件内容越多实行功率越慢,而且一次只能实行一个文件。

(这儿原本想录个屏,发现现在新增一个组件生成起来需求几十秒了,所以仍是不上传了)

简略分析下 pigeon 耗时原因,一个问题它是 dart 脚本,和 java 写脚本相同启动虚拟机环境需求时刻,二是它运用了 dart AST 解析东西 analyzer,这个或许也是不完善的,解析上耗时很高。

那我们做了哪些优化?

按模块分包

组件库按模块分红多份,fgui、part_home、part_video 等等,这样来平衡 pigeon 脚本实行次数和文件内容巨细两者的关系。

实行跳过逻辑

在分包后,简略的依据组件包内的 ui_components.yaml 文件 sha1 标识来做了一个实行跳过逻辑。

简略代码暗示:

run_base.rb

module BaseGenerator
  ...
  def BaseGenerator.excute
    paths = get_yml_paths()
    paths.each do |path|
      models = fetch_models(path)
      $path_models[path] = models
      # 核算 YAML 文件 sha1
      sha1 = $script_version + ":" + Digest::SHA1.hexdigest(File.read(path))
      # 读取缓存的 sha1 值
      save_sha1 = read_yaml_version(path)
      if sha1 != save_sha1
        # 假设不相等,则实行生成指令
        puts "[fgui_component_api]: -- Generating from #{path}"
        ...
      else
        # 跳过
        puts "[fgui_component_api]: -- skip from #{path}"
      end
      ...
      # 保存其时版别号
      write_yaml_version(path, sha1)
    end
  end
end

版别号保存在躲藏文件里 .ui_components.version

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

前面的 1.0.3 是脚本其时的版别,这样来用于脚本生成代码发生变化时,可以控制让各开发同学都更新,而不是用旧的。

很简略的规划,但很有用,而且可以前进打包机 CI 功率。

生成 Examples

Example 为什么也做生成?除了简洁开发外,还有一点是能引导开发供应完善的测试用例。

这在生成上做了三件事:

  1. 生成索引目录(一级、二级)
  2. 生成 example.dart
  3. 生成路由跳转

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

新增组件后,只需求编码红框 body 部分即可运用调试。

当然,这个功用只对新增的组件有用,会提早判别是否已生成 example 代码。

生成说明文档

有心的同学可以看到流程图上“生成 Docs” 部分的样式跟其他的不同,那是因为这个是实行独自的指令,但也是包括在 FGUIComponentAPI 东西链中。

(实行进程比较长,转成 gif 比较慢,所以视频压缩了下,用糊的)

除了第一步也是要无缺生成组件库代码外,这儿需求多实行一步:[fgui_component_api]: Generating preivew part

里边也是做了2件事:

构建 Web 产品

Flutter for Web 是真的好用,特别在做组件预览上,这儿有几个要害点需求留心,flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=https://x.gdm.gaoding.com/artifacts/doc/canvaskit/ --web-renderer canvaskit;

我们的打包语句如上,--web-renderer canvaskit 是为了预览上烘托一致性。--dart-define=FLUTTER_WEB_CANVASKIT_URL= 是指定到内网 canvaskit 目录下,不然会很慢。

还有一点需求留心的是,打包出来的 web 是肯定途径定位的 <base href='https://juejin.im/'>,因为布置原因,最好换成相对的,这可以直接在 /web/index.html 中修正,也可以像笔者相同,在脚本上增加一个替换即可,这样可以少提交代码。

index_html_content = index_html_content.gsub("\<base href=\"/\"\>", "\<base href=\"./\"\>")

生成 Markown

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

我们移动端在线文档是用的 vuepress 做的,所以这儿需求供应符合规范的 Markdown 文件即可。

这个的做法也类似,供应模版代码,生成 .mdflutterComponentsSidebar.js 侧边栏控制。

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

需求手动提 PR 来触发文档更新。

控制政策版别

这儿用 git 分支来完结版别号控制,这样各调用端可以提交分支号来保证提交记录可回溯。

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇

其他漏说了一句,FGUIComponentAPI 产品也和其他东西产品一起放入在 application-services 应用服务中。这个应用服务还没有完全清楚,待完善后会独自写一篇文章来做介绍。

还做了什么

生成代码上我们还做许多事,比如一致的 SLS 性能收集,供应设备信息,供应埋点方法等等。

乃至可以把网络通讯等放进去,但这个不是通用组件的完结领域,所以并不考虑。但也有在规划用 Flutter 多引擎方案做页面来替代 flutter_boost 完结,或许会用类似的方法供应一套 Flutter 多引擎间的状况办理。

未来规划

版别自动化

这儿面版别控制问题并没有完全处理,Flutter 版别也需求跟 Native 调用端代码保持一致,那这个版别号改变放到代码合入流程里最为适合,但这之间也有蛮多的前置 CI 问题需求处理。

去除 pigeon

生成功率在其时阶段也还算是可以忍耐的,但最佳实践肯定是去除掉 pigeon,用模版代码替代 AST 解析完结,这样整个进程就可以达到毫秒级。

感想

本篇是本系列收尾篇,年前终究一篇文章了。也是对整年作业学习的一个总结:知道本质、考究方法、达到政策

整套方案也是存在各种的不足点,终究笔者精力仍是有限的。做到极致仍是做到可用?是要考虑公司的全体规划和利益,终究也仅仅一个普普通通的天选“冬阴工”[手动狗头]。能写在上也是因为公司支撑开源,不搞敝帚自珍,陆陆续续把记录在公司文稿上的记录整理了过来。

下一年文章的方向上仍是会在跨端开发领域,但或许不再集合于 Flutter 技能链上。终究处理问题的方法并不重要,最重要的是选择适合的方法达到政策,即“不择方法”[手动狗头]。

终究说句祝福,期望下一年是一个好年,疫情向下,经济向上。我们下一年共济沧海 ~


感谢阅读,假设对你有用请点个赞 ❤️

从零开始|构建 Flutter 多引擎烘托组件:跨端东西链篇