Flutter 多仓库本地 Monorepo 方案与体验优化
# 一、前言
在上一篇 Flutter - Melos Pub workspaces 实践 中我们探讨了 Monorepo 实践,但这与我们当前多仓库、多业务包的现状不符。我们希望在维持多仓库管理模式的同时,也能利用 Melos 和 Pub workspaces 的优势。
本文的主题 —— “拼好包”,就是为解决这一问题而提出的方案。
https://github.com/LinXunFeng/local_pub_workspace (opens new window)
# 二、调整
延用上一篇的工作区 lxf_workspace,不同之处在于,我们会按需将其他独立仓库的 app 工程和业务包通过 Git 拉取放置在 apps 和 packages 目录下,但这些文件仅存放于本地,并不会提交到工作区仓库。
# .gitignore
在 apps 和 packages 目录下都创建名为 .gitkeep 的空白文件,并将如下内容添加至 .gitignore。
# .gitignore
!apps/.gitkeep
!packages/.gitkeep
这样可保持这两个空目录同步至 lxf_workspace 仓库,别人拉下来就不用再手动创建他们了。
# 工作区结构
这里以上一篇中的工作区仓库为例,结构如下
.
├── 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 后自动执行的,无须手动操作。
# 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 需做如下调整
# 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 项目而言,这种自动生成方式会导致:
- 配置冗长混乱: 生成的运行配置列表过于庞大。
- 项目难以区分: 当多个包内存在同名
example目录时,调试选项会变得模糊不清,严重影响开发效率。

为解决上述问题,我实现了 update-launch 命令,该命令旨在提供一个简洁、高效的调试配置管理方案:
- 智能解析: 自动解析项目根目录
pubspec.yaml文件中的workspace配置。 - 精准生成: 为工作区内的每个 Flutter 包生成命名清晰、易于辨识的运行配置。生成时,优先为包的
example目录创建配置;若无example目录,则直接为包本身创建配置。例如,只会保留apps/app_a和packages/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 选项来隐藏当前开发任务不需要的包。例如,将 package_b 的路径加入该配置即可在文件浏览器中隐藏它,从而让我们更专注于相关代码。
{
"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.yaml 的 workspace 配置,并在 settings.json 中动态注释/取消注释 files.exclude 规则,只显示当前工作区所需项目。
该脚本已配置为在 melos bs 命令后自动运行,无需手动干预。
# 优化代码分析压力
随着 packages 数量的增加,即使在 VSCode 中隐藏了不相关的项目,Dart 分析器仍会分析所有代码,这会导致分析速度显著变慢,影响开发效率。
解决方案:通过在 analysis_options.yaml 文件中配置 analyzer.exclude,明确排除不需要分析的目录。
示例:若要排除 package_b,可按如下方式配置:
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 的打包流程面临两大核心挑战:
Pub workspaces限制与resolution: workspace冲突:app_a的pubspec.yaml中声明了resolution: workspace,这表示它依赖于工作区内的包。然而,当app_a被单独拉取或在非Monorepo环境下构建时,由于无法解析workspace依赖,会导致依赖拉取失败。为了解决这个问题,在打包时需要将此配置注释掉或重写。- 功能分支开发与
Git依赖管理:app_a通常依赖于master分支上的业务包,例如:
# 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_a 的 dependency_overrides 中重写 package_a 的依赖,并提交到版本库。
然而,在 app_a 和 package_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 示例内容如下:
# 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.yaml中workspace和dependency_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_a 在 Monorepo 环境下的依赖管理和打包难题。
研发流程:
- 更新
workspace配置,取消需求涉及的包的注释。 - 执行
melos bs,自动完成包依赖解析和配置更新。 - 专注于功能开发。
打包流程:
- 执行
melos run update_app_pubspec_overrides。 - 提交
app下更新的pubspec_overrides.yaml。 - 触发
Jenkins打包,apply_app_pubspec_overrides.sh脚本将自动激活打包配置。
好了,本篇到此结束,这两篇 Monorepo 的应用只是结合我自身使用场景去阐述的,如有不同意见,以你的为准,欢迎在评论区留下你的见解。

- 01
- Flutter webview 崩溃率上升怎么办?我的分析与解决方案10-19
- 02
- Flutter - Melos Pub workspaces 实践10-12
- 03
- Flutter - 详情页初始锚点与优化08-24