敞开生长之旅!这是我参与「日新方案 12 月更文应战」的第15天,点击查看活动概况
前文传送门
《从零开始|构建 Flutter 多引擎烘托组件:先导篇》 《从零开始|构建 Flutter 多引擎烘托组件:Flutter 工程篇》 《从零开始|构建 Flutter 多引擎烘托组件:Flutter 代码篇》 《从零开始|构建 Flutter 多引擎烘托组件:Native 调用篇》
前些文章比较重视源码解说,所以堆了较多的代码来做具体说明,这行为有些吃力不讨好 – -|
本篇会用更少的代码以及更多的考虑来具体描述我们是怎样落地 Flutter 多引擎烘托组件开发东西链的。
引题
先想想我们构建跨端 UI 组件库的政策是什么?除了 UI 一致性烘托外,最重要的仍是降本增效啊。终究假设公司肯用“爆兵战术”不计本钱的话啥能做不出来?(说的是有用本钱,当然要扫除小马哥那种内部贪腐的比如[狗头])。要用有限的资源来做更大的“瑞士卷”,这即符合往后的发展趋势,也是“秃头”程序员一生所寻求的(啊 bushi)。
而在前文的开发流程中,写一个 Flutter 多引擎烘托组件真实真实是太费事了,虽然我们用了 pigeon 减少了一部分通讯代码,但剩余的代码流程也足以让一个开发变成退堂鼓选手。怎样处理开提问的问题,才是本系列的重中之重。
方案
先回想下笔者在先导篇中所说的 FGUIComponentAPI
, 有看过之前几篇文章的同学应该会对它有所形象,但 FGUIComponentAPI
并不单指跨端组件库,也不单是今天所说的跨端东西链,它是全体方案的集合概念,包括配套环境、开发流程、成果产品。
再回到本文所要讲的跨端东西链,要处理的就是开发流程的问题。简化开发流程,前进开发领会。
《提到跨端东西链,我们是在说什么?》,对跨端东西链概念迷惑的同学可以看一下笔者这篇文章。
完结作用
先看一下现在的运用作用
在 VSCode 中实行 command + shift + p
选择 GaodingFlutter: run generate api
指令后,即会自动依据组件定义生成了相应的根底代码,包括前几篇文章内所讲的 api.dart
、base.dart
、.h / .m
、.kt
等。
这儿还要其他说明一下,生成的文件我们选择不上传 git,优点有许多:减少无效 code review,防止提交抵触,生成成果改变对开发者来说无感知。而带来的代价是需求在组件库变化后,需求开发实行一下指令才华正常运转。当然我们在 VS Code Extension 中也如 flutter pub get
指令相同,做了更新提示。
《构建 VS Code Extension,前进 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 多引擎烘托,在稿定 App 的实践(二):原理篇》 中有具体描述,这儿不再做赘述。
(定义文件放在组件包内,便于维护)
我们在脚本代码里,也是有定义相关的类,当然,直接用 map
也没什么问题,脚本言语考究没那么多,这儿方法上选的 Ruby
,用 Python
或许 Node
也都可以,但不要用 dart 脚本,功率太低。
复制资源文件
如上图所示的 Android 部分,终究我们生成后是一个独立的插件包,所以必要的需求仿制一些固定资源,这样减少模版代码的作业量。
构造模版代码
什么是模版代码?直接贴图举例,仍是上图 Android 模版中,直接看预览:
其实模版就是把真实代码中需求变量的部分,用特定的字符串替代,例如 ${NAME}
,然后在脚本代码中做全局替换即可,是不是十分简略 ~
脚本代码里要做的事,一是类型转化,二是代码字符串拼装即可。
类型转化
我们在 YAML 用的是 Flutter 语法做 type
定义,这儿需求转成其他端言语的运用方法,这儿举例比较恶心的 OC 代码,就需求供应额定的东西方法来处理修饰符:
以及 *
指针的问题。
这一点除了官方有供应 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
前面的 1.0.3 是脚本其时的版别,这样来用于脚本生成代码发生变化时,可以控制让各开发同学都更新,而不是用旧的。
很简略的规划,但很有用,而且可以前进打包机 CI 功率。
生成 Examples
Example 为什么也做生成?除了简洁开发外,还有一点是能引导开发供应完善的测试用例。
这在生成上做了三件事:
- 生成索引目录(一级、二级)
- 生成 example.dart
- 生成路由跳转
新增组件后,只需求编码红框 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
我们移动端在线文档是用的 vuepress 做的,所以这儿需求供应符合规范的 Markdown 文件即可。
这个的做法也类似,供应模版代码,生成 .md
和 flutterComponentsSidebar.js
侧边栏控制。
需求手动提 PR 来触发文档更新。
控制政策版别
这儿用 git 分支来完结版别号控制,这样各调用端可以提交分支号来保证提交记录可回溯。
其他漏说了一句,FGUIComponentAPI 产品也和其他东西产品一起放入在 application-services 应用服务中。这个应用服务还没有完全清楚,待完善后会独自写一篇文章来做介绍。
还做了什么
生成代码上我们还做许多事,比如一致的 SLS 性能收集,供应设备信息,供应埋点方法等等。
乃至可以把网络通讯等放进去,但这个不是通用组件的完结领域,所以并不考虑。但也有在规划用 Flutter 多引擎方案做页面来替代 flutter_boost 完结,或许会用类似的方法供应一套 Flutter 多引擎间的状况办理。
未来规划
版别自动化
这儿面版别控制问题并没有完全处理,Flutter 版别也需求跟 Native 调用端代码保持一致,那这个版别号改变放到代码合入流程里最为适合,但这之间也有蛮多的前置 CI 问题需求处理。
去除 pigeon
生成功率在其时阶段也还算是可以忍耐的,但最佳实践肯定是去除掉 pigeon,用模版代码替代 AST 解析完结,这样整个进程就可以达到毫秒级。
感想
本篇是本系列收尾篇,年前终究一篇文章了。也是对整年作业学习的一个总结:知道本质、考究方法、达到政策。
整套方案也是存在各种的不足点,终究笔者精力仍是有限的。做到极致仍是做到可用?是要考虑公司的全体规划和利益,终究也仅仅一个普普通通的天选“冬阴工”[手动狗头]。能写在上也是因为公司支撑开源,不搞敝帚自珍,陆陆续续把记录在公司文稿上的记录整理了过来。
下一年文章的方向上仍是会在跨端开发领域,但或许不再集合于 Flutter 技能链上。终究处理问题的方法并不重要,最重要的是选择适合的方法达到政策,即“不择方法”[手动狗头]。
终究说句祝福,期望下一年是一个好年,疫情向下,经济向上。我们下一年共济沧海 ~
感谢阅读,假设对你有用请点个赞 ❤️