likes
comments
collection
share

跨端工具链,在稿定终端研发中的实践

作者站长头像
站长
· 阅读数 6

笔者的感概: 从现在的趋势看,App 开发者未来终究会是终端工程师,而终端工程师要会的技能除了要了解各端开发实现外,更重要的是具备串联各端、各语言的能力。

引言

去年写了篇文章,讲了下笔者理解的跨端工具链是什么样的。

在补齐跨端通信能力(GNB)后,整套工具链在稿定终端开发中已经是一个成熟体系了,可以系统的来介绍下我们在其中是做了些什么,是怎样落地的。

能力概览

跨端工具链是一个抽象概念,目的是把端与端的关系通过工具链条联系起来。

在具体实现上,我们是落在终端服务层(application-services),从物理架构上看,它就是一个独立的 git 仓库。

跨端工具链,在稿定终端研发中的实践

包括生成服务、终端组件、开发辅助扩展三个部分。

跨端工具链,在稿定终端研发中的实践

那我们如何来理解终端服务层

我们结合 DevOps 的概念来看,终端服务其实是在 Dev 前置一步的环节中,为了开发服务的一层,往往是在构建前或者构建过程中参与编译使用。

生成服务

生成服务(codegen)类似工厂车间,它可以按照规范输入,依赖固定模板,生成规范输出的各终端产物。

工具语言选型上,早期多使用 Ruby 语言,原因是为了开发使用方便,毕竟公司内部都是用 Mac 开发,MacOS 内置了 Ruby 环境,没有什么预安装成本,适合前期探索落地。后续,考虑到与打包机、Dock 容器等环境保持一致,所以逐渐迁移成 Python 来开发。

无论是什么语言开发,都会增加 shell 文件main.sh来保证使用入口的一致性。Flutter 组件化工具以及跨端通信协议还会增加preview.sh来生成 markdown 预览文档。

在规范输入上,也会根据实际需求有一些差异,因为数据来源是不一致的,有些服务需要读取服务端接口,有些自闭环的可以写在生成服务本身,还有一些可以写在各个项目中,通过一定规范即可让工具读取到。

那在产物输出上也有区别,有些是作为文件直接生成到项目的模块中,功能上相对独立的会生成产物 SDK,作为终端组件

终端组件

凡是可以打包成 SDK 的,终究会被打包成 SDK。

我们在前期工具链设计上,产物尽量是一个独立的终端组件,以轻量为主。目的就是在后续稳定期可以提供独立的 cocoapods / gradle / npm / pub / ... 组件库,甚至是提供二进制库的形式来减少构建成本。

在组件角色上,现在是划分的比较明确:调用组件/支撑组件。顾名思义,比如 GNB 消息通信能力在 iOS / Android / 桌面端是支撑组件实现,而 Web / Flutter 是调用组件实现。

但这也不是绝对的,比如后续 Rust 加入进来,那 Rust 侧就是支撑组件的形式,而其他代码侧则是构建调用组件。也比如可能也会让 Flutter 来支撑 Web 开发。

在使用上,只要项目集成就可以具备相应的跨端能力,抹平各个平台差异性。

组件 SDK 化的另一个好处是调用 API 是不变的,对于上层业务来说不关心是如何实现的,SDK 如何变化,也无需改变业务代码,更改生成模版代码即可。

开发辅助扩展

可自动化的终究会自动化,可 AI 实现的终究会让我们失业[狗头]。

什么是开发辅助扩展? 提高开发效率,降低人为因素导致的意外问题的工具。我们更想把它理解成稿定终端 IDE的前身,现在是在VS Code上初步建设了Web & App Flutter & App 两个开发辅助扩展,用于把整个跨端开发构建流程自动化起来,减少开发同学心智成本,让开发只关心一端代码即可。

后续演变上,IDE建设就是把项目、运行终端设备、开发辅助扩展三者结合起来,让 Web / Flutter 工程直接在 App 项目中上构建运行调试。

落地方式

前面是介绍我们在稿定终端开发中跨端工具链都做了什么,也用终端服务层(application-services) 的形式把它们结合在一起。

但其实有一个十分重要问题,如何把终端服务层(application-services) 落地到具体的各个项目中,这问题的实质是在多个仓库代码如何有机的结合在一起。

这个问题也有很多种解法,笔者一一列出,然后讲一下我们的最佳实践。

方案选型

二方库/三方库

构建完成后组件上传到内部仓库(二方库)或者上传到 github / npmjs / pub / ..(三方库),当作独立组件来使用。

按理说这算是最终形态,但如果改动频繁就涉及到发包的问题、业务项目也要频繁的修改包版本号,对小范围内部开发来讲不友好。而且每个组件都需要独立一个版本号,版本号管理起来成本也十分大,比如发包前就要检查各个组件的版本清单。

Git 子模块(submodule)

Git 对于复杂多项目也提供了一个解决方案,使用 submodule 子模块的方式,把组件作为一个独立的仓库加入到业务仓库中。

我们的最佳实践

最后我们使用的这个方式姑且称作脚本仓库管理,市面上确实没有人说有这么做,但效果确实意外的好。

到底是个什么样的方案?

一句话描述:各终端仓库通过增加脚本代码setup_application_services.sh,自行通过各个语言环境的工具链,集成到项目构建过程中。

setup_application_services.sh

# 链接应用服务
 
#!/bin/sh
 
VERSION=0.1.1 # 使用的版本号,后续讲解作用
 
CURPATH=$(
  cd "$(dirname "$0")"
  pwd
)
cd $CURPATH
 
DIRECTORY=../application-services # as 服务下载位置,建议放到项目根目录
if [ ! -d "$DIRECTORY" ]; then
    git clone git@git.gaoding.com:gdmobile/application-services.git $DIRECTORY
fi
 
cd $DIRECTORY
git clean -f
git reset --hard
git remote update origin
git checkout $VERSION
git pull origin $VERSION
cd ..

详细讲一下这个脚本的作用:

  • 直接把application-services仓库下载到工程内部,作为一个完整的子文件夹。
  • 清理掉可能被开发操作的仓库改动。
  • 拉取该分支下最新的代码。

这样来保证每次执行脚本,都可以获得一个符合版本预期的正确仓库内容。

VESION常量实质上是指定git branch分支号,只不过我们生成名称为版本号的分支来实现的更优雅。(在开发测试中,笔者都是新增开发分支VERSION=dev/xxxx来做)

这种方式带来的好处:

  • 版本号可以统一管理。
  • 保证开发环境的干净,每次执行脚本都会还原一些意外的误改动。
  • 可当作普通组件修改测试,只要不执行脚本,就可以直接修改其中代码来验证问题。

唯一的坏处是拉取的是整个仓库,跟工程无关的代码也会被拉取下来,但这是可接受的,对于开发而言只是硬盘多占用一些,在开发过程中不受影响。

各端落地方式

稿定在各端项目上都是采用 Monorepo 多项目大仓管理的模式,所以我们需要的也是把application-services中的组件也当作它的一个项目,这样来抹平开发的认知成本。

也不能让开发同学手动去调用脚本,这样也太不优雅了,我们还需要把它融入到整个集成流程中。

下面分开讲下不同的工程我们都是如何做的(划重点)。

iOS

iOS 需要在 Pod 之前提前执行application-services.sh

首先需要在Podfile中最前面增加模块引入。

跨端工具链,在稿定终端研发中的实践

这样让 Pod 执行前先去执行manager.rb文件。

跨端工具链,在稿定终端研发中的实践

manager.rb 中关键代码:

# 拉取最新的 application-services
def setup_application_services
  Pod::UI.puts "check git application-services".green
  system File.join($PROJECT_PATH, "tools", "setup_application_services.sh")
end

在执行pod install / pod update时,就会先去执行setup_application_services.sh脚本。

跨端工具链,在稿定终端研发中的实践

然后是如何使用大仓模式呢?

这里推荐大家两个 Ruby 插件:cocoapods-monorepo 、cocoapods-headermap,用于大仓管理的,这都开源在 RubyGems(前同事留下的成果 ~)。

这里增加application-services下载后的文件夹索引路径即可。

跨端工具链,在稿定终端研发中的实践

但开源版本只支持传单个 path,需要进行一些扩展改造,建议下载插件源码到本地来使用。

Android

Android 比较简单些,只要新增一个 Gradle 执行文件即可。

application_services.gradle

task Setup(type: Exec) {
    exec {
        commandLine rootDir.getParentFile().getParent() + '/tools/setup_application_services.sh'
    }
}

而 Monorepo 公司 Android 是采用的手动指定,但也完全可以通过一个 Gradle 来遍历查询。

Flutter

Flutter 比较麻烦一点,因为flutter pub get流程并不适合做钩子,所以我们采用了另一种形式,利用 VS Code ExtensionFlutter 开发辅助工具增加命令来帮助开发把这个流程自动化。

跨端工具链,在稿定终端研发中的实践

Monorepo 也是通过手动指定依赖路径。

Web

Web 脚手架比较完善,很容易集成进去。

跨端工具链,在稿定终端研发中的实践

scripts 增加一下调用即可。

而 Monorepo 前端就更成熟了,我们使用 pnpm 的大仓方案即可。

pnpm-workspace.yaml

packages:
  - "src/*"
  - "../application_services/*"

总结

当前的跨端工具链建设只是开始,当下在稿定内部也逐渐推广起来,慢慢扩散到各个团队中去使用,未来上还是有更多的可能性。


感谢阅读,如果对你有用请点个赞 ❤️

跨端工具链,在稿定终端研发中的实践