
你有没有遇到过这种情况:
项目里有 5 个互相依赖的微服务,每次改一个公共库,要手动
npm link/go mod tidy好几次; A 项目用 React 18.2,B 项目用 18.3,版本冲突烦到想砸电脑; CI 跑一次要 40 分钟,其中 30 分钟在重复下载依赖; 3 个仓库来回切换,代码复制粘贴到怀疑人生……
如果你点头了,那今天这篇文章,就是为你准备的。
2025-2026 年,几乎所有规模稍大的团队都在用 monorepo + workspace:
这三种方案各有什么优势?适合什么场景?
今天我们就来一次彻底对比,看看你该 pick 哪一个!
先把这些痛点列出来,看看你中了几个👇
多个子项目之间有大量共享代码:公共类型、工具函数、配置文件、测试工具……
每次改动底层公共包,上游所有项目都要手动 npm link / go mod edit / cargo publish,操作繁琐到让人怀疑人生。
仓库一多,依赖版本冲突、重复下载、node_modules 爆炸、vendor 目录重复编译,直接让你怀疑电脑是不是该换了。
lint / test / build 要跑很多次,构建时间越来越长,咖啡都喝了好几杯,CI 还没跑完。
跨项目重构、批量升级依赖、统一代码风格……光是想想就头皮发麻,还是算了吧。
Workspace 的核心思想很简单:
把这些有强耦合关系的包放在同一个仓库里,用工具统一管理依赖、构建、脚本、版本。
就像把散落的珍珠串成项链,一站式解决所有烦恼。
Rust 的 workspace 是 Cargo 原生支持的,语法极其简洁:
# Cargo.toml (workspace root)
[workspace]
members = [
"crates/core",
"crates/cli",
"crates/server",
"crates/sdk",
]
resolver = "2" # 强烈建议开启就这几行配置,能给你带来什么?
整个 workspace 只产生一个 target/ 目录,所有 crate 共享编译产物。
修改 crates/core 后,cli、server、sdk 都能直接享受到增量编译。
不像独立仓库那样每个项目都重新下载 + 编译同一份依赖。
那种感觉就像从绿皮火车换成了高铁🚀
在 workspace 根目录写 [workspace.dependencies],所有子 crate 都可以直接继承:
[workspace.dependencies]
tokio = { version = "1.42", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
anyhow = "1.0"
thiserror = "1.0"子 crate 只需写 tokio.workspace = true 即可,版本漂移问题?不好意思,不存在的。
cargo build --workspace
cargo test --workspace
cargo clippy --workspace --all-targets
cargo fmt --all
cargo publish -p my-crate-*一条命令搞定所有子包,CI 配置瞬间清爽得像刚洗完的头🚿
想在本地同时调试某个第三方依赖?直接用 path patch,几秒钟切换,再也不用发版到 crates.io 反复测试了。
resolver = "2" 基本成为标配workspace.dependencies + dependency table 组合拳,打遍天下无敌手一句话总结:Rust workspace 像它的语言风格一样——简洁、强大、零负担。
TypeScript 生态的 workspace 经历了 npm → yarn classic → yarn berry → pnpm 的演进,目前公认最好用的是 pnpm + workspace 或 yarn berry + workspace。
典型结构:
monorepo/
├── packages/
│ ├── core/
│ ├── ui/
│ ├── admin/
│ └── cli/
├── pnpm-workspace.yaml
├── tsconfig.json # 根配置 + composite 项目引用
└── package.jsonpnpm 使用硬链接 + 符号链接 + 内容寻址存储,相同版本的包在整个 monorepo 只存一份。
10 个子包都用 React 18.3.0?磁盘只占一份。
再也不用看着几十 G 的 node_modules 流泪了😭
pnpm 默认不 hoist,可以精细控制哪些包提升到根 node_modules,极大减少 "phantom dependencies"(幽灵依赖)。
妈妈再也不用担心我引用了没在 package.json 里声明的包了!
"dependencies": {
"@myorg/core": "workspace:*",
"@myorg/ui": "workspace:*"
}改动 @myorg/core 后,admin、cli 立刻能感知到最新代码,无需 publish,即改即用。
那种丝滑感,就像德芙巧克力🍫
开启 composite + project references 后,tsc 能进行增量构建 + 类型检查缓存,大型 monorepo 编译速度可提升 3-10 倍。
10 分钟变成 1 分钟,这种快乐谁用谁知道!
统一的脚本、lint、test、storybook、changeset 等工具链,turbo / nx / lage / moonrepo / rush 这些工具几乎都假设你在用 monorepo + workspace。
一句话总结:pnpm + turbo 是 TypeScript 生态的"豪华套餐",磁盘和速度双赢。
Go 1.18 正式引入了 workspace 功能(go work 命令),让 Go 项目也能享受多包同仓的快感。
# 1. 初始化 workspace
go work init
# 2. 添加子模块
go work use ./core ./cli ./server ./sdk
# 3. 查看 workspace 配置
go work edit生成的 go.work 文件结构如下:
// go.work
go 1.22
use (
./core
./cli
./server
./sdk
)就这么简单!不需要额外的配置文件,Go 就是这么直接。
所有子模块共享同一个 go.work,修改依赖版本只需在 workspace 级别执行:
go get -r golang.org/x/text@latest所有子模块瞬间统一升级,再也不用一个个改了。
Go 编译器本身就是增量编译的高手,配合 workspace 后,多个模块间的编译产物可以共享缓存。
修改 core 包后,server 直接享用编译结果,CI 时间大幅缩短。
想在本地同时开发某个依赖库?直接把它的路径加进来:
go work use ./core ./my-local-dependency无需 go mod edit -replace,无需发布到私有仓库,调试第三方库就像调试本地代码一样爽。
my-go-workspace/
├── go.work # workspace 配置文件
├── core/
│ ├── go.mod # module github.com/myorg/core
│ └── *.go
├── cli/
│ ├── go.mod # module github.com/myorg/cli
│ └── *.go
├── server/
│ ├── go.mod # module github.com/myorg/server
│ └── *.go
└── sdk/
├── go.mod # module github.com/myorg/sdk
└── *.go每个子目录都是独立的 Go module,可以单独 go build、go test,也可以在 workspace 下一键编译全部。
一句话总结:Go workspace 就像 Go 语言本身——简单、直接、不玩虚的。
维度 | Rust | TypeScript (pnpm) | Go |
|---|---|---|---|
配置方式 | Cargo.toml | pnpm-workspace.yaml | go.work |
语法简洁度 | ⭐⭐⭐⭐⭐ 极简 | ⭐⭐⭐⭐ 较简洁 | ⭐⭐⭐⭐⭐ 极简 |
依赖版本统一 | [workspace.dependencies] 原生支持 | workspace:* + overrides | go work 统一管理 |
磁盘占用 | target/ 较大(但只一份) | pnpm 硬链接,磁盘占用最小 | 编译缓存可共享 |
增量编译 | cargo 增量极强 | tsc --build + project references | Go 编译器原生支持 |
命令批量操作 | cargo --workspace 原生支持 | 需 turbo/nx 辅助 | go build ./... 原生支持 |
本地开发体验 | path 依赖 + patch | workspace:* 超丝滑 | go work use 最直接 |
工具链生态 | cargo 足够强大 | turbo/nx/lage/rush 百花齐放 | 官方支持,生态发展中 |
CI 缓存 | target/ 整体缓存 | pnpm + turbo 缓存更灵活 | Go 模块缓存 + 本地编译缓存 |
学习成本 | 低 | 中(工具链多) | 极低 |
最适合场景 | 高性能系统、基础设施 | 前端/全栈大型项目 | 后端微服务、云原生 |
大型公司往往会同时使用多种技术栈:
公司 monorepo/
├── frontend/ # TypeScript + pnpm + turbo
├── backend-core/ # Go + go work
├── infrastructure/ # Rust + cargo
└── shared/ # 共享 proto、配置等核心原则:让工具适应人,而不是人适应工具。
Rust、TypeScript、Go 三大社区虽然语言不同,但工程诉求高度一致:
"强相关的代码就应该放在一起,强相关的依赖就应该统一管理,强相关的构建产物就应该共享缓存。"
[workspace.dependencies] —— 极简到令人发指go work 命令的直白美学 —— 简单直接不玩虚的三者殊途同归,最终都指向同一个目标——让中大型项目的维护成本大幅下降,让开发者把精力真正放在写业务逻辑上,而不是在版本冲突和重复构建中挣扎。
评论区等你来聊!
如果这篇文章对你有帮助,点个在赞,让更多程序员少走弯路🚀