Flutter 正在切换成 Monorepo 和支持 workspaces
其实关于 Monorepo 和 workspaces 相关内容在之前《Dart 3.5 发布,全新 Dart Roadmap Update》 和 《Flutter 之 ftcon24usa 大会,创始人分享 Flutter 十年发展史》 就有简单提到过,而目前来说刚好看到 flaux 这个新进展,所以就再展开来聊聊目前 Flutter 里进行中的 Monorepo 和 workspaces。
其实如果你不跑引擎,不提 PR 或者不看源码,那 monorepo 调整对你来说应该没什么正向影响。
monorepo
可能有人还没听说过 monorepo,先介绍下 monorepo(mono repository) ,它是一种项目代码的管理方式,就是将多个项目存储在一个 repo 中,在 monorepo 里多个项目的所有代码都存储在单个仓库里,这个集中式存储库包含 repo 中的所有组件、库和内部依赖项等。
monorepos 也不是什么新概念,很早之前 Google、Meta、Microsoft、 Twitter 等公司都在大规模使用 monorepos ,比如 React
、 Vscode
、Babel
都使用了 monorepo,如下图所示可以看出来他们在形式和结果的区别:
更多可见原文:https://blog.bytebytego.com/p/ep62-why-does-google-use-monorepo
使用 monorepo 的最主要原因之一是简化代码管理,由于所有代码都存储在单个存储库中,因此可以更轻松地跟踪更改、保持版本一致性等,例如 Meta 的工程师曾就表示过:
Meta 的 monorepo 包含公司的大部分代码,可以轻松访问所有代码对开发人员的效率非常重要,工程师可以更深入地了解其依赖关系,调试整个堆栈中的问题,并实施功能或错误修复,所有这些都触手可及······
······版本控制是使用多个存储库时最复杂的问题之一,每个存储库都是独立的,团队可以自由决定要采用的依赖项版本,但是由于每个存储库都按照自己的节奏发展,这些不一致会导致项目可能包含同一依赖项的多个版本,从而导致后续版本控制的各种冲突·····
https://www.growingdev.net/p/what-it-is-like-to-work-in-metas
所以 monorepo 不仅仅只是将所有源代码“紧密放在一起”,更是确保存储库中各个库和应用相互兼容的重要工具:
简单点说人话就是:将现在 Flutter 项目下的
flutter/engine
、flutter/buildroot
和flutter/flutter
这三个存储库合并到单个 flutter/flutter 存储库中。
采用 monorepo 对于目前 Flutter 来说有着许多好处,例如更方便的 CI 和更容易协作,同时提高代码共享的能力,例如在现在的 flutter/flutter
项目下,由于 flutter/flutter
和 flutter/engine
是两个独立项目,所以经常可以看到一堆”无意义“的 Roll Engine PR 的记录存在:
“Roll Flutter Engine from····” 主要是为了更改 framework 引用的 engine 的版本,每次 engine 有新的提交的时候,都会在 framework 自动创建一个 PR,更新 engine ,当所有测试通过时,PR 就会自动合并。
而如果使用 monorepo , flutter/flutter
和 flutter/engine
将不再是相互隔离的 repo ,在 CI 和版本管理上都会更轻便可控,例如一开始文章提到的 flutter/flaux
,就是目前 Flutter 正在测试合并流程的项目,在最终合并到主flutter/flutter
之前验证 monorepo 的仓库。
也就是
flutter/engine
这个 repo 最终会在未来的某个时间点被 Archive ,虽然这和 Flutter 以前解释说不适用 monorepo 的原因相违背, 但是这个阶段来看,Flutter 还是需要选择 monorepo。
对于 monorepo 而已,所有依赖都是一个来源,所以意味着不存在版本冲突和依赖地狱,同时 monorepo 还支持原子提交,在大规模重构里开发人员可以在一次提交中更新多个包或项目。
其实目前 pub 上很多项目都在使用类似 monorepo 的结构,例如 riverpod 、cfug/dio 、ubuntu/app-center 、Flame-Engine/Flame 等项目都在使用 melos 做 monorepos 管理,而 Flutter 这次也是通过自定义对应的 CI 基础能力来更新以支持合并后的 repo 构建,也就是 Flutter 针对 monorepo 结构自定义了一套新的 CI 系统,事实上 monorepo 核心之一,就是在于 CI 的搭建还有项目结构分层的处理上。
更多可见:https://melos.invertase.dev/~melos-latest/#projects-using-melos 、https://github.com/orgs/flutter/projects/182/
总结
总结起来,Flutter 迁移到 monorepo 的好处在于:
-
提交原子性:engine、framework 和 buildroot 的变更可以一起提交,更好管理
-
减少依赖 :可以减少大量内部版本依赖和冲突问题,大规模减少类似 Roll 类型的历史提交等
-
简化 PR:用户可以更直观和方便提交 PR,包括这个 PR 需要从 engine 到 framework 多方调整的时候
-
更方便测试: engine 和 framework 可以在同套 CI 下测试调整
当然 monorepo 也带来了新的问题,例如一个 repo 下多个 Dart 项目的解析问题,这就不得不说下面的 workspaces 概念。
workspaces
在 Dart 3.5 的时候,Dart Roadmap 就提出了 pub workspaces 概念,核心就是在 monorepo 中实现多个相邻包的共享解析,比如「共享解析」可以解决类似编辑器中的 Analyzer 占用空间过大问题,因为它可以减少单独的上下文 context。
analysis_options.yaml 下的大量规则引入也会增加内存上涨,事实上 Dart Analyzer 的上下文数量一直是内存消耗大户。
在 dart#53874 提到过,由于 monorepo 结构化的原因,Analyzer 在工作的时候最终会为每个包及其所有依赖项加载了多个重复的 analysis contexts,从而导致 monorepo 里每个包 analysis 时在内存中生成了多个副本,最终出现内存占用过大问题:
而解决方案就是在这些 repo 中为每个依赖项创建一个共享解决方案,也就是这里提到的 pub workspaces
,通过 workspaces ,项目在 monorepo 中可以实现多个相邻包的共享解析,通过共享 Analyzer 等工具在分析每个包之间共享上下文,从而节省大量内存。
也就是 workspaces 可以让 monorepo 创建共享版本来优化依赖项解析速度和内存。
例如,你可以通过 workspace
关键字启用 workspaces 并引入对应的 packages 结构:
name: workspace
environment:sdk: ^3.5.0
workspace:- packages/package_a- packages/package_b
然后在 packages/package_a/pubspec.yaml
和 packages/package_b/pubspec.yaml
添加一行 resolution: workspace
:
name: package_a
environment:sdk: 3.5.0
resolution: workspacedependencies:# You can depend on packages inside the workspace:package_b: ^1.2.4 # This version constraint will be checked against the local version
dev_dependencies:test: ^1.0.0
此时你的项目就已经处于 workspace 的工作模式下,而 workspaces 主要是支持 workspaces 下 monorepo 的包之间的共享解析,例如在仓库中的任意位置运行 flutter pub get
,项目会将产生一个共享解析,具体表现为:只会在 root pubspec.yaml
同级目录生成 一个 root pubspec.lock
。
另外,通过 dart pub deps 也可以更清晰看到整个 workspace 的依赖关系,也可以更好管理和同步依赖,结合 monorepo 结构或者 melos 等工具的能力,会让整个项目更直观可控:
最后
可以看到 monorepo 和 workspaces 属于相辅相成的存在,而 monorepo 也不是单纯就是把东西放到一个 repo ,更多涉及项目结构的分层,包的细化还有最关键的 CI 工具支持等,同时 monorepo 也可以更好管理所有项目的依赖关系,甚至支持增量构建等等。
总的来说 monorepo 并不是什么新鲜东西,只是到了这个阶段 Flutter 所需要做的一个调整,之前采用 multi repo 是规划里把多个 repo 当作独立产品,而现在是作为一个整体项目来管理的情况下,自然选择 monorepo 更合适。
参考资料
-
https://github.com/dart-lang/sdk/issues/53875
-
https://github.com/dart-lang/pub-dev/pull/7762
-
https://github.com/dart-lang/pub/issues/4391
-
https://github.com/dart-lang/pub/issues/4127
-
https://docs.google.com/document/d/1UEEAGdWIgVf0X7o8WPQCPmL4LSzaGCfJTM0pURoDLfE/edit?resourcekey=0-c5CMaOoc_pg3ZwJKMAM0og&tab=t.0
-
https://github.com/flutter/engine/blob/main/pubspec.yaml
-
https://github.com/orgs/flutter/projects/182
-
https://github.com/flutter/flaux
-
https://github.com/flutter/cocoon
相关文章:

Flutter 正在切换成 Monorepo 和支持 workspaces
其实关于 Monorepo 和 workspaces 相关内容在之前《Dart 3.5 发布,全新 Dart Roadmap Update》 和 《Flutter 之 ftcon24usa 大会,创始人分享 Flutter 十年发展史》 就有简单提到过,而目前来说刚好看到 flaux 这个新进展,所以就再…...

小白初入Android_studio所遇到的坑以及怎么解决
1. 安装Android_studio 参考:Android Studio 安装配置教程 - Windows(详细版)-CSDN博客 Android Studio超级详细讲解下载、安装配置教程(建议收藏)_androidstudio-CSDN博客 想下旧版本的android_studio的地址(仅供参考…...
NetCore使用Aop和内存缓存对接口、方法进行数据缓存
通过Aop内存缓存对接口、方法进行缓存 源码地址https://gitee.com/wangbenchi66/nuget 1. nuget包引入 必须引入包 至少在2024.11.7以上 <PackageReference Include"WBC66.Cache.Core" Version"2024.11.7" />必须开启内存缓存 否则后续步骤无法正…...
playwright学习记录2--定位方式
快捷导航 定位方式:元素操作断言方式自动等待 定位方式: csspage.get_by_role() 通过显式和隐式可访问性属性进行定位。page.get_by_text() 按文本内容定位。page.get_by_label() 通过关联标签的文本定位表单控件。page.get_by_placeholder() 通过占位符…...

响应式网页设计--html
一,HTML 文档的基本结构 一个典型的 HTML 文档包含了几个主要部分,基本结构如下(本文以下出现的所有代码都可以套入下面示例进行测试): <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8&q…...
C#核心(8) 静态成员
前言 先前我们已经学习了成员变量以及成员属性。 静态成员对于在整个应用程序中共享数据和功能非常有用。它们可以用于跟踪全局状态、共享常量和实现单例模式等。但是需要注意的是,过度使用静态成员可能导致代码变得难以维护和测试,因此应谨慎使用。其…...

关于git使用的图文教程(包括基本使用,处理冲突问题等等)超详细
目录 用户签名,初始化git git提交流程图 提交到本地库 版本穿梭 分支操作 分支合并冲突 团队协作 github的使用 推送代码 克隆 拉取代码 团队协作冲突 团队协作之分支管理 推送分支到分支: 拉去远程库分支到本地库: 本地删除远程分支&am…...
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
在前端开发的广阔天地中,Axios 犹如一颗璀璨的明星,为我们与服务器之间的通信搭建起坚实的桥梁。其中,responseType 属性更是赋予了我们灵活处理服务器响应的强大能力。 一、Axios 的 responseType 属性值及示例 1.arraybuffer 当我们将 r…...

redis集群介绍
1. 节点(Node): • Redis集群中的单个Redis服务器实例。每个节点都运行一个Redis服务器进程,并维护自己的数据。 2. 分片(Sharding): • 将数据集分割成多个部分,并分布到不同的节点…...
JDK中常用的包有哪些?
1.java.lang 描述:包含Java语言的核心类,不需要显式导入。 常用类:Object、String、Math、System、Thread、Exception等。 2.java.util 描述:提供了集合框架、日期和时间功能、随机数生成、扫描和格式化等实用工具类。 常用类…...

校园官网练习---web
HTML: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>西安工商学院</title><…...
MySQL中指定字段的某个值排在前面
一 需求 如果我们想讲表中指定的字段的某一个值排序在最前面应该如何处理? 二 实现方式 方法 1、使用<>,xml中使用<![CDATA[跳过解析的特殊符号]]>或者<(小于符号)>(大于符号) ORDER …...

【51单片机】I2C总线详解 + AT24C02
学习使用的开发板:STC89C52RC/LE52RC 编程软件:Keil5 烧录软件:stc-isp 开发板实图: 文章目录 AT24C02介绍存储器 I2C总线介绍I2C时序结构数据帧AT24C02数据帧 编程实例 —— 按键控制数据大小&存储器写入读出 AT24C02介绍 …...
直接插入排序法
任务描述 本关任务:要求编写程序实现直接插入排序的功能。 相关知识 直接插入排序,是指将无序序列中的各元素依次插入到已经有序的数组中。 假设数组中前 i-1 元素已经有序,现在要将线性表中第 i 个元素插入到前面的有序子表中,…...
mysql中InnoDB索引与MyISAM索引
mysql索引 InnoDB 索引存储 主键索引(聚簇索引) 定义:主键索引是 InnoDB 存储引擎的聚簇索引,它决定了表中数据的物理存储顺序。每个 InnoDB 表都有一个且仅有一个聚簇索引。存储:主键索引的叶子节点直接包含表的数…...

Redis如何保证数据不丢失(可靠性)
本文主要以学习为主,详细参考:微信公众平台 Redis 保证数据不丢失的主要手段有两个: 持久化 多机部署 我们分别来看它们两的具体实现细节。 1.Redis 持久化 持久化是指将数据从内存中存储到持久化存储介质中(如硬盘…...

【计网】物理层学习笔记
【计网】物理层 物理层概述 物理层要实现的功能 在各种传输媒体上传输比特0和1,进而为上面的数据链路层提供透明传输比特流的作用。 物理层接口特性 物理层之下的传输媒体 传输媒体是计网设备之间的物理通路,也称为传输介质。 传输媒体并不包含在…...
vue链接跳转
在 Vue 3 的组合式 API 中,你可以使用 ref 和 setup 函数来实现外部链接跳转功能。 方法 1:使用 click 和 window.open(新标签页跳转) 这种方式在点击时会打开一个新标签页并跳转到外部链接。 <menu-item value"item2&…...

IP地址是电脑自带的吗?是根据什么而决定的
IP地址并非电脑自带,而是由网络运营商或网络服务提供商通过特定的协议和机制进行分配和管理的。要深入理解IP地址的来源和决定因素,我们需要从IP地址的基本概念、分配方式以及影响分配的因素等多个方面进行探讨。 IP地址,即互联网协议地址&am…...

JavaFX史上最全教程 - Shape - JavaFX矩形椭圆
avaFX Shape类定义了常见的形状,如线,矩形,圆,Arc,CubicCurve,Ellipse和QuadCurve。 在场景图上绘制矩形需要宽度,高度和左上角的(x,y)位置。 要在JavaFX中…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...

ui框架-文件列表展示
ui框架-文件列表展示 介绍 UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...

RocketMQ 客户端负载均衡机制详解及最佳实践
延伸阅读:🔍「RocketMQ 中文社区」 持续更新源码解析/最佳实践,提供 RocketMQ 专家 AI 答疑服务 前言 本文介绍 RocketMQ 负载均衡机制,主要涉及负载均衡发生的时机、客户端负载均衡对消费的影响(消息堆积/消费毛刺等…...

【Redis】Redis 的持久化策略
目录 一、RDB 定期备份 1.2 触发方式 1.2.1 手动触发 1.2.2.1 自动触发 RDB 持久化机制的场景 1.2.2.2 检查是否触发 1.2.2.3 线上运维配置 1.3 检索工具 1.4 RDB 备份实现原理 1.5 禁用 RDB 快照 1.6 RDB 优缺点分析 二、AOF 实时备份 2.1 配置文件解析 2.2 开启…...