Flutter 多仓库本地 Monorepo 方案与体验优化

欢迎关注微信公众号:FSA全栈行动 👋

一、前言

在上一篇 Flutter - Melos Pub workspaces 实践 中我们探讨了 Monorepo 实践,但这与我们当前多仓库、多业务包的现状不符。我们希望在维持多仓库管理模式的同时,也能利用 MelosPub workspaces 的优势。

本文的主题 ------ "拼好包",就是为解决这一问题而提出的方案。

github.com/LinXunFeng/...

二、调整

延用上一篇的工作区 lxf_workspace,不同之处在于,我们会按需将其他独立仓库的 app 工程和业务包通过 Git 拉取放置在 appspackages 目录下,但这些文件仅存放于本地,并不会提交到工作区仓库。

.gitignore

appspackages 目录下都创建名为 .gitkeep 的空白文件,并将如下内容添加至 .gitignore

bash 复制代码
# .gitignore

!apps/.gitkeep
!packages/.gitkeep

这样可保持这两个空目录同步至 lxf_workspace 仓库,别人拉下来就不用再手动创建他们了。

工作区结构

这里以上一篇中的工作区仓库为例,结构如下

shell 复制代码
.
├── README.md
├── analysis_options.yaml
├── apps
│   ├── .gitkeep
│   └── app_a
├── melos.yaml
├── packages
│   ├── .gitkeep
│   ├── package_a
│   ├── package_b
│   ├── package_c
│   └── ...
├── pubspec.lock
├── pubspec.yaml
├── script
│   └── apply_app_pubspec_overrides.sh
└── tool
    ├── README.md
    ├── commands
    │   ├── dependency_overrides.dart
    │   ├── setup_workspace.dart
    │   ├── update_analysis_options.dart
    │   ├── update_app_pubspec_overrides.dart
    │   ├── update_launch_json.dart
    │   └── update_settings_json.dart
    ├── core
    │   └── workspace_manager.dart
    └── main.dart
文件(夹) 作用
analysis_options.yaml Dart 代码分析配置文件,主要用来减少不必要的分析
apps 【本地】 存放壳工程,或其它 app 工程
packages 【本地】 存放各个仓库的工程,如:业务工程,组件库
pubspec.yaml 声明 workspace,重写依赖,定义 Melos 脚本
script 存放了各种辅助脚本
tool 存放了各种辅助命令

tool 目录下,存放了一些十分有用的辅助命令,基本上都是在 melos bs 后自动执行的,无须手动操作。

yaml 复制代码
# pubspec.yaml

melos:
  scripts:
    prepare: melos bootstrap
    clean: melos clean
    pub_upgrade:
        run: flutter pub upgrade
    # 更新工作区的 launch.json 文件
    update_launch_json: dart tool/main.dart update-launch
    # 更新工作区的 settings.json 文件
    update_settings_json: dart tool/main.dart update-settings
    # 更新工作区的 analysis_options.yaml 文件
    update_analysis_options_yaml: dart tool/main.dart update-analysis
    # 更新所有 app 工程的 pubspec_overrides.yaml 文件
    update_app_pubspec_overrides: dart tool/main.dart update-pubspec-overrides
    # dependency_overrides 本地仓库执行 flutter pub get
    deps_overrides_get: dart tool/main.dart dependency-overrides --get
    # dependency_overrides 本地仓库执行 flutter pub upgrade
    deps_overrides_upgrade: dart tool/main.dart dependency-overrides --upgrade
    # 设置工作空间依赖
    setup_workspace: dart tool/main.dart setup-workspace
    setup_workspace_no: dart tool/main.dart setup-workspace --no-workspace

  command:
    bootstrap:
      hooks:
        # 在 bootstrap 执行后执行
        post: |
          echo "更新 VSCode settings.json 配置..."
          melos run update_settings_json
          echo "更新 VSCode launch.json 配置..."
          melos run update_launch_json
          echo "更新 analysis_options.yaml 配置..."
          melos run update_analysis_options_yaml
          # melos run deps_overrides_get
          melos run deps_overrides_upgrade

注意,因为在研发过程中,我们只关心当前被集成的包,如本地 packages 目录下有十几个包,但我的需求只需要我处理 package_a,则工作区的 pubspec.yaml 需做如下调整

yaml 复制代码
# pubspec.yaml
workspace:
  - apps/app_a
  - packages/package_a
  - packages/package_a/example
  # - packages/package_b
  # - packages/package_b/example
  ...

建议把 packages 下的业务包都列出来,这样大家拉下来后只需要做取消注释操作,而不需要自己手动添加。

以下内容均以该配置进行说明!

三、辅助命令与脚本

项目运行

VSCode 虽提供自动生成 launch.json 的便捷功能(删除旧文件后点击创建 launch.json 文件),但对于包含多个 Flutter 包的 Monorepo 项目而言,这种自动生成方式会导致:

  1. 配置冗长混乱: 生成的运行配置列表过于庞大。
  2. 项目难以区分: 当多个包内存在同名 example 目录时,调试选项会变得模糊不清,严重影响开发效率。

为解决上述问题,我实现了 update-launch 命令,该命令旨在提供一个简洁、高效的调试配置管理方案:

  1. 智能解析: 自动解析项目根目录 pubspec.yaml 文件中的 workspace 配置。
  2. 精准生成: 为工作区内的每个 Flutter 包生成命名清晰、易于辨识的运行配置。生成时,优先为包的 example 目录创建配置;若无 example 目录,则直接为包本身创建配置。例如,只会保留 apps/app_apackages/package_a/example 的配置,如果 package_a 没有 example,则为 packages/package_a

通过在 melos 中设置钩子,update-launch 脚本会在执行 melos bs 命令后自动运行。这确保了 .vscode/launch.json 文件始终保持动态更新,从而在 VSCode 的"运行和调试"面板中呈现一个精简、有序且易于选择的调试选项列表,极大提升了 Monorepo 环境下的开发体验。

隐藏无用项目

为了保持工作区整洁,避免 packages 目录下项目过多造成干扰,我们可以通过配置 .vscode/settings.json 中的 files.exclude 选项来隐藏当前开发任务不需要的包。例如,除了 app_apackage_a,将其它项目的路径加入该配置即可在文件浏览器中隐藏它,从而让我们更专注于相关代码。

json 复制代码
{
  "files.exclude": {
    // "**/apps/app_a/**": true,
    // "**/packages/package_a/**": true,
    "**/packages/package_b/**": true,
    "**/packages/package_c/**": true,
    "**/packages/package_d/**": true,
    "**/packages/package_e/**": true
  },
}

当项目包增多时,手动修改 settings.json 切换文件可见性效率低下,而 update-settings 命令可自动化此过程!

它会读取 pubspec.yamlworkspace 配置,并在 settings.json 中动态注释/取消注释 files.exclude 规则,只显示当前工作区所需项目。

该脚本已配置为在 melos bs 命令后自动运行,无需手动干预。

优化代码分析压力

随着 packages 数量的增加,即使在 VSCode 中隐藏了不相关的项目,Dart 分析器仍会分析所有代码,这会导致分析速度显著变慢,影响开发效率。

解决方案 :通过在 analysis_options.yaml 文件中配置 analyzer.exclude,明确排除不需要分析的目录。

示例 :若要排除 package_b,可按如下方式配置:

yaml 复制代码
analyzer:
  exclude:
    # - apps/app_a/**
    # - packages/package_a/**
    - packages/package_b/**

为了简化此过程,我已实现自动化。

update-analysis 脚本会在 melos bs 执行后自动运行,根据当前工作区动态更新 analysis_options.yaml 文件,确保分析器始终保持高效。

打包

Monorepo 架构能有效提升代码复用和管理效率。然而,当项目采用多仓库、多业务包模式时,如何将 Monorepo 的优势与现有工作流结合,并解决特定打包难题,是我们需要面对的挑战。

本文聚焦于 app_a 的打包问题,并提出一套基于 pubspec_overrides.yaml 的解决方案。

app_a 打包面临的挑战

在当前的多仓库 Monorepo 环境下,app_a 的打包流程面临两大核心挑战:

  1. Pub workspaces 限制与 resolution: workspace 冲突app_apubspec.yaml 中声明了 resolution: workspace,这表示它依赖于工作区内的包。然而,当 app_a 被单独拉取或在非 Monorepo 环境下构建时,由于无法解析 workspace 依赖,会导致依赖拉取失败。为了解决这个问题,在打包时需要将此配置注释掉或重写。
  2. 功能分支开发与 Git 依赖管理app_a 通常依赖于 master 分支上的业务包,例如:
yaml 复制代码
# app_a/pubspec.yaml
environment:
  sdk: ">=3.6.0 <4.0.0"
resolution: workspace

dependencies:
  package_a:
    git:
      url: git@code.gitlab.com:lxf/package_a.git
      ref: master

当需要对 package_a 进行功能开发时(例如在 feature/拼好包分支 上),传统的做法是在 app_adependency_overrides 中重写 package_a 的依赖,并提交到版本库。

然而,在 app_apackage_a 都处于 workspace 下的 Monorepo 结构中,这种直接重写会导致冲突和报错,无法正常打包。


解决方案:pubspec_overrides.yaml 动态配置

为了解决上述挑战,我采用了基于 pubspec_overrides.yaml 的动态配置方案,它将开发与打包时的依赖管理进行分离:

1、生成预备配置 (update_app_pubspec_overrides)

在开发阶段,通过执行 update_app_pubspec_overrides 命令,为每个 app 工程(例如 app_a)生成一个 pubspec_overrides.yaml 文件。这个文件包含了所有打包所需的重写规则,例如修正 resolution 配置、指定 Git 分支依赖等。关键在于,这些规则在生成时全部被注释掉。此文件会随需求分支一同提交到版本控制系统。

生成的 pubspec_overrides.yaml 示例内容如下:

yaml 复制代码
# resolution:
# dependency_overrides:
# # ====== app 工程中的 dependency_overrides
# # ====== workspace 和 dependency_overrides 项目的 git 依赖
#   package_a:
#     git:
#       url: git@code.gitlab.com:lxf/package_a.git
#       ref: feature/拼好包分支

该文件主要包含以下几类重写:

  • 重写 resolution: 配置。
  • 同步 app 工程下 pubspec.yaml 中定义的 dependency_overrides
  • 同步工作区 pubspec.yamlworkspacedependency_overrides 部分的 Git 依赖。

2、打包时应用配置 (apply_app_pubspec_overrides.sh)

Jenkins 打包流程开始前,执行 apply_app_pubspec_overrides.sh 脚本。该脚本会自动取消 pubspec_overrides.yaml 文件中的注释,使其中定义的打包专用重写规则生效。这样,打包系统就能正确解析依赖,顺利完成构建。

当需求分支合并到 master 后,pubspec_overrides.yaml 文件应还原为初始的注释状态(例如只包含 # resolution:)。update-app-pubspec-overrides --restore 提供了还原功能,建议通过 CI/CD 流程自动完成此还原操作。

当然,这里的打包只是个简单的解决思路,请结合自身情况处理即可。

四、最后

通过 pubspec_overrides.yaml 动态配置方案,我们实现了开发与打包流程的解耦,有效解决了 app_aMonorepo 环境下的依赖管理和打包难题。

研发流程:

  1. 更新 workspace 配置,取消需求涉及的包的注释。
  2. 执行 melos bs,自动完成包依赖解析和配置更新。
  3. 专注于功能开发。

打包流程:

  1. 执行 melos run update_app_pubspec_overrides
  2. 提交 app 下更新的 pubspec_overrides.yaml
  3. 触发 Jenkins 打包,apply_app_pubspec_overrides.sh 脚本将自动激活打包配置。

好了,本篇到此结束,这两篇 Monorepo 的应用只是结合我自身使用场景去阐述的,如有不同意见,以你的为准,欢迎在评论区留下你的见解。

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
非凡ghost4 小时前
ProcessKO(查杀隐藏危险进程)多语便携版
前端·javascript·后端
yinuo5 小时前
你的网页还不会"看人"?3分钟让它拥有会追踪的眼睛
前端
IT小番茄5 小时前
kubernetes云平台管理实战:deployment通过标签管理pod(十)
架构
守正出琦5 小时前
带代码示例的 HTML 标签实操手册
前端·html
yume_sibai5 小时前
HTML HTML5基础(1)
前端·html·html5
XianZhe_5 小时前
Pug 哈巴狗 便捷的HTML预处理器 上
前端·html·pug·html预处理器
yume_sibai5 小时前
HTML HTML5基础(2)
前端·html·html5
守正出琦5 小时前
HTML 常用标签速查表
前端·javascript·html
吃饺子不吃馅6 小时前
Canvas实现协同电影选座
前端·架构·canvas