Flutter - Melos Pub workspaces 实践

作者:LinXunFeng日期:2025/10/13

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

一、前言

为解决 App 代码臃肿、编译耗时的问题,我们进行了分包重构,核心思路如下:

  1. 业务分包:将不同业务线的代码拆分成独立的包,开发者只需聚焦于各自包内的 example 工程进行开发,从而提升编译和运行效率。
  2. 功能沉淀:把跨业务复用的功能(包括基础业务和非业务功能)也抽离成独立的包,逐步让主 App 轻量化为一个“空壳”,负责集成所有模块。
  3. 依赖管理:业务包之间使用 git 依赖,指向 master 分支;而非业务的功能包则发布到自建的 unpub 平台,通过版本号管理。
1cache:
2  version: ">=1.0.0 <2.0.0"
3  hosted:
4    url: http://unpub.lxf.dev/
5package_a:
6  git:
7    url: [email protected]:lxf/package_a.git
8    ref: master
9

分包后虽然利于单个工程的独立开发,但一旦涉及跨包联调,就会变得非常低效。

开发者需要手动修改 dependency_overridespath 方式指定本地依赖,在多个 IDE 窗口间切换,并耗费大量时间处理依赖冲突,这在紧张的工期中尤其痛苦。

因此,在官方的 Pub workspaces 方案出现之前,Melos 的出现有效解决了这些痛点。

二、Melos

Melos 是一个管理多个项目的 CLI 工具,只需要在 melos.yaml 中的 packages 下声明各个包,如下所示

1# melos.yaml
2packages:
3  - packages/package_a
4  - packages/package_a/example
5  - packages/package_b
6  - packages/package_b/example
7

执行 melos bs 即可自动在各个包中创建 pubspec_overrides.yaml 并重写必要的包依赖,顺带执行 flutter pub get

生成的 pubspec_overrides.yaml 内容如下

1# pubspec_overrides.yaml
2# melos_managed_dependency_overrides: package_a ...
3dependency_overrides:
4  # 同步 pubspec.yaml 中的 dependency_overrides
5  scrollview_observer: ^1.26.2
6
7  # 如果只依赖了 package_a,则只重写 package_a 依赖
8  package_a:
9    path: ../../packages/package_a
10

除此之外,它还提供了脚本功能,方便我们在各个包中去并发执行命令,如下所示

1# melos.yaml
2scripts:
3  fluttergen:
4    # 各个包都执行
5    exec: fluttergen
6  pod_install:
7    exec: pod install --project-directory=ios
8    packageFilters:
9      # 只在各个包的 example 中执行
10      scope: "*example"
11  iconfont:
12    exec: sh ./run.sh
13    packageFilters:
14      # 只在 package_a 中执行
15      scope: package_a
16  pub_upgrade:
17    run: flutter pub upgrade
18    exec:
19      # 设置并发数
20      concurrency: 1
21

可配置执行范围,比如只在某个包或各个包的 example 下执行命令;配置执行的并发数等。定义的脚本通过 melos run 去执行,如 melos run iconfont

还可以做到自动生成 CHANGELOG 和发包到 pub 等,有兴趣的小伙伴可自行到官网了解更多的功能。

尽管 Melos 功能强大,但它仍有两个核心缺陷:

  1. 依赖冲突:由于每个包都维护着独立的 pubspec.lock 文件,Melos 无法从根本上保证所有包的依赖版本一致。
  2. 内存占用Dart 分析器会为每个包创建独立的分析上下文,导致了额外的内存开销。

三、Pub workspaces

Dart 3.6.0 的时候,官方带来了 Pub workspaces,解决了上述问题,它的优缺点总结如下

优点

优点描述
统一依赖管理所有工作区的包共享一个 pubspec.lock 文件,确保依赖版本一致性,避免版本冲突。
性能优化Dart 分析器为整个工作区创建单一分析上下文,减少内存占用,提升大型仓库的分析性能。
简化操作只需在仓库根目录运行一次 dart pub get,即可为所有工作区包获取依赖。
自动本地解析工作区内包之间的相互依赖会自动解析到本地版本,无需手动配置 path 依赖。
灵活的依赖覆盖支持在根 pubspec.yaml 或 pubspec_overrides.yaml 中进行依赖覆盖。
便捷的命令执行可以使用 -C 选项在特定工作区包中执行 pub 命令,无需切换目录,如:flutter pub get -C apps/app_a。
清晰的包列表dart pub workspace list 命令可列出所有工作区包及其路径。

缺点

缺点描述
迁移成本现有 Monorepo 迁移到 Pub workspaces 需要修改 pubspec.yaml 文件并确保 SDK 约束(至少 ^3.6.0)。
“游离”的 pubspec.yaml 文件工作区根目录与工作区包之间存在非工作区成员的 pubspec.yaml 文件会导致 pub get 报错。
依赖覆盖限制一个包只能被覆盖一次。
版本约束匹配即使使用本地版本,包之间的依赖版本约束仍需匹配。
发布行为差异工作区包发布到 pub.dev 时,将使用托管版本的依赖,而非工作区内的本地版本。

Pub Workspaces 的一个核心原则:整个工作区只有一个统一的依赖解析。

这意味着在工作区的根目录会有一个全局的 pubspec.lock 和一个全局的 .dart_tool/package_config.json 文件,所有工作区内的包都使用这两个文件来管理它们的依赖。

四、迁移优化成效

这是从 Melos 迁移至 Melos + Pub Workspaces 后的提升数据

  • 设备:M1 16G
  • 方式:集成 37 个包(包含 example)并等待代码分析完成

优化前

优化后

优化前优化后提升
CPU 时间9:35.467:43.6919.4%
内存占用12.23 GB2.62 GB78.6%

这里说一点,基于 Pub Workspaces 实现的 MonorepoMelos 并不是必要的,只是它提供的脚本功能和 Hook 实在是太方便了,有助于提升效率,所以建议搭配使用!

下面我们进入实战

五、实战

环境要求

Dart 版本需 >=3.6.0,对应 Flutter 版本需 >=3.27.0

全局安装 Melos

1dart pub global activate melos "7.0.0-dev.8"
2

如果你之前有安装过 Melos,可以先卸载再安装

1dart pub global deactivate melos
2

那为什么要指定 7.0.0-dev.8 这个版本?这是因为我们现在使用的 Flutter 版本是 3.29.3,对应的 Dart 版本是 3.7.2,结合 Melos 的当前部分版本要求,如下所示

VersionMin Dart SDKFlutter 版本
7.1.13.93.35.0
7.0.03.93.35.0
7.0.0-dev.103.83.32.0
7.0.0-dev.83.63.27.0

所以只能挑个 7.0.0-dev.8 先用用,如果你已经用上了 Flutter 3.35.0,则可使用当前最新正式版 7.1.1

初始化仓库

创建 workspace 仓库(当然,你也可以基于现有仓库进行改造),名字你随意,这里我以 lxf_workspace 为例。

创建 pubspec.yaml,内容如下

1name: lxf_workspace
2publish_to: none
3
4environment:
5  sdk: ^3.6.0
6dev_dependencies:
7  # 与全局安装的保持一致
8  melos: 7.0.0-dev.8
9
10workspace:
11  - apps/app_a
12  - packages/package_a
13  - packages/package_a/example
14  - packages/package_b
15  - packages/package_b/example
16

Dart 3.11 开始支持 globs 语法,可以让 pubspec 更加干净,如下所示

1workspace:
2  - apps/**
3  - packages/**
4

调整所有工程包位置,如壳工程存放至 apps 目录,其它包存放至 packages

工作区结构

1.
2├── README.md
3├── apps
4│   └── app_a
5├── melos.yaml
6├── packages
7│   ├── package_a
8│   ├── package_b
9│   ├── package_c
10│   └── ...
11├── pubspec.lock
12└── pubspec.yaml
13
文件(夹)作用
apps存放壳工程,或其它 app 工程
packages存放各个仓库的工程,如:业务工程,组件包
pubspec.yaml声明 workspace,重写依赖,定义 Melos 脚本

调整 pubspec.yaml

lxf_workspace 中涉及到的包(即 workspace 下声明的那些),其 pubspec.yaml 都需要做如下两个调整

  • environment.sdk>=3.6.0
  • 新增 resolution: workspace

如下所示

1# pubspec.yaml
2environment:
3 sdk: ">=3.6.0 <4.0.0"
4resolution: workspace
5

注意,这些包可以有 dependency_overrides,但不可以同时对同一个包进行重写,否则会冲突!所以建议将这些重写统一放到 lxf_workspacepubspec.yaml中。

启动

执行 melos bs

注意:这里再强调一遍,melos bs 不是应用 Pub Workspaces 的必要流程,使用 Melos 的主要原因是为了使用其脚本功能和 Hook 来提效,如果你不需要这些,也可以直接使用 flutter pub get

1❯ melos bs
2melos bootstrap
3> /Users/lxf/lxf_workspace
4
5Running "flutter pub get" in workspace...
6  > SUCCESS
7
8Generating IntelliJ IDE files...
9  > SUCCESS
10
11 -> 5 packages bootstrapped
12

根据上述的 Pub Workspaces 的核心原则,它会将所有包的 pubspec.lock 都删除,所有包都会新增 .dart_tool/pub/workspace_ref.json 并指向根,即 lxf_workspace 目录。

如果你需要更新依赖,则直接在 lxf_workspace 目录下执行 flutter pub upgrade 即可,这些跟原来的一样。

如果你想重写一些第三方库,可以在 pubspec.yaml 中的 dependency_overrides 进行重写

1# pubspec.yaml
2...
3dependency_overrides:
4  scrollview_observer: ^1.26.2
5  # chat_bottom_container:
6  #   path: packages/chat_bottom_container
7
8workspace:
9  ...
10

在完成这些调整后,后续的开发流程还是跟原先一样,只是现在统一在一个 IDE 窗口中操作罢了,这里就不再赘述。

六、最后

以上便是基于将所有内容(workspaceappspackages)都上传至一个大型仓库,并统一管理的 Monorepo 方案的实践。

而对于想继续多仓库管理工程包的我来说,还需要对该方案进行改造,因为我觉得分久必合,合久必分是迟早的事,再加上我也比较懒~

好了,下一篇来讲讲我的本地 Monorepo 的 "拼好包" 方案和一些优化。

资料

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


Flutter - Melos Pub workspaces 实践》 是转载文章,点击查看原文


相关推荐


业务流程建模标准(BPMN)
deepdata_cn2025/10/11

在数字化转型浪潮中,企业对业务流程的可视化、标准化与自动化需求日益迫切。BPMN(Business Process Model and Notation,业务流程建模符号) 作为全球通用的业务流程建模标准,通过统一的图形语言打破了“业务人员说不清楚、IT人员看不懂”的沟通壁垒,成为连接业务需求与技术实现的核心桥梁。 一、BPMN的起源与发展 在BPMN出现前,企业建模缺乏统一规范:有的用流程图(Flowchart),有的用UML活动图,甚至有的用手绘草图——不同角色对同一流程的理解差异巨大,导致


JDK8 新特性 - Stream 流详解
chirrupy_hamal2025/10/9

文章目录 一、认识 Stream二、Stream 的常用方法1、如何获取 Stream 流2、Stream 流常见的中间方法2.3、Stream 流常见的终结方法 一、认识 Stream 二、Stream 的常用方法 1、如何获取 Stream 流 2、Stream 流常见的中间方法 代码简化 s -> s.getName() Studet::getName 代码简化 2.3、Stream 流常见的终结方法 报错


一个基于 ASP.NET Core 的开源、模块化、多租户应用框架和内容管理系统
追逐时光者2025/10/8

前言 今天大姚给大家分享一个基于 ASP.NET Core 的开源、模块化、多租户应用框架和内容管理系统:OrchardCore。 项目介绍 OrchardCore 是一个开源的(BSD-3-Clause license)、模块化的、支持多租户的应用程序框架,使用 ASP.NET Core 构建。同时,它也是一个基于该框架的内容管理系统(CMS)。 DotNetGuide编程学院 DotNetGuide编程学院是一个专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享的知识


Python 的 UDP 编程
hubenchang05152025/10/6

#Python 的 UDP 编程 用户数据报协议(User Datagram Protocol) 是一个 无连接、非可靠 的传输层协议,和 TCP 并列,是互联网中最常见的协议之一。 UDP 程序不存在连接,只需要绑定自身地址并收发数据即可。下面是一个示例,它创建了两个 socket,从一个向另一个发送数据。 import socket # 创建 UDP socket sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock2


QPS和TPS的区别,在实际项目中,如何准确测量和监控QPS和TPS?
IT橘子皮2025/10/5

QPS(Queries Per Second,每秒查询率)和TPS(Transactions Per Second,每秒事务数)是衡量系统性能的两个关键指标,虽然常被混淆,但存在以下核心区别: 1. ​定义与范围​ ​QPS​:表示服务器每秒能响应的查询次数,通常用于衡量特定查询服务器(如DNS、数据库)的处理能力。例如,一次页面加载可能触发多次查询(如HTML、CSS、JS请求),每个查询均计入QPS。 ​TPS​:表示每秒完成的事务数量。一个事务涵盖客户端请求、服务器内部处理及响应的完整流


豆包怎么部署到本地?一文读懂AI工具的本地化安装全流程
Nightowls__2025/10/4

在数据自主性与即时响应需求的双重推动下,AI 模型的本地部署成为众多企业和个人用户的追求,豆包作为热门 AI 模型,其本地部署也备受关注。豆包本地部署在哪里?又该如何实现高效且安全的本地化运行呢? 一、为什么要本地部署豆包? 在数据自主性与即时响应需求的双重推动下,AI模型的本地部署逐渐成为企业及个人用户优化服务、保障隐私的关键策略。对于豆包这一主流AI模型而言,本地部署具有显著的优势: 满足多元场景需求:不同行业和领域对AI模型的应用需求千差万别,本地部署豆包能够确保模型在复杂网


vue2动态实现多Y轴echarts图表,及节点点击事件
四月_h2025/10/3

父组件 <template> <div class="app-container"> <div class="content"> <el-form :model="echartsqueryParams" ref="echartsqueryForm" :inline="true" > <el-form-item label="号" prop="furnaceNumber"> <e


神经网络中的损失函数:常见类型与应用场景(代码演示)
fyakm2025/10/2

在神经网络的世界里,损失函数是一个非常重要的概念。它就像是一个“裁判”,能够衡量模型预测结果与真实结果之间的差距。通过了解损失函数的常见类型和应用场景,我们可以根据不同的需求选择合适的损失函数,从而避免因损失函数选择不合理导致的模型性能不佳问题。接下来,我们就一起深入了解一下损失函数的相关知识,并通过Python代码来演示不同损失函数的计算和效果。 目录 损失函数的类型和应用场景均方误差损失函数(Mean Squared Error, MSE)交叉熵损失函数(Cross - Entro


《Local_Pdf_Chat_RAG 深度学习笔记:PDF 本地化对话的 RAG 原理与实践》
Bug Spray2025/10/2

在处理本地 PDF 文档的智能对话需求时,很多人会面临 “云端依赖泄露隐私”“开源项目部署门槛高” 的问题 —— 而 GitHub 上的 Local_Pdf_Chat_RAG 项目,恰好以 “本地化部署 + RAG 技术” 为核心,解决了 PDF 文档的高效检索与自然语言对话难题。近期我系统学习了这个项目,从环境搭建、代码核心模块拆解,到实际场景测试与调优,踩过不少新手常见的坑,也总结了一套可复用的实践经验。因此整理这份学习笔记,一方面记录自己的技术成长,另一方面希望能帮到同样想入门 “本地 P


【mdBook】7.2 替代后端
liuyuan7710/2/2025

如果我们不需要指定字数统计后端的完整名称/路径,是因为 mdbook 会尝试按照约定推断程序的名称。foo后端的可执行文件通常称为mdbook-foo,并在book.toml中有相关的条目。要明确告诉 mdbook 调用什么命令,可以使用command虽然这个示例是人为设计的,但希望它能足够展示如何为 mdbook 创建替代后端。本章开头提到的现有后端应该作为实际如何完成的良好示例,因此请随时浏览源代码或提出问题。

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0