- 发布日期
第 02 讲|Monorepo 结构与发布包关系
解析 vercel/next.js monorepo 结构、各包发布关系与依赖图
阶段一:框架认知与源码导航 · 第 2 / 40 讲 难度:⭐⭐ · 预计耗时:1.5 小时
学习目标
- 5 分钟内定位"我要改的功能在哪个包里"。
- 区分发布包(用户安装的)、内部包(仅本仓库使用)、Rust crates(编译进 SWC/Turbopack)的边界。
- 能说清
packages/next与packages/next-swc与turbopack/三者的关系。
1. 整体俯瞰
仓库根有 7 个核心目录:
next.js/
├── packages/ ← JS / TS 包(pnpm workspaces)
├── crates/ ← Next.js 自己的 Rust crates(SWC plugin、turbopack 绑定)
├── turbopack/ ← Turbopack 子树(git subtree from vercel/turborepo)
├── apps/ ← 内部 demo / dogfooding 应用
├── bench/ ← 性能基准
├── test/ ← e2e / 集成 / 单元测试
├── docs/ ← 公开文档(next.js.org 的源)
└── examples/ ← create-next-app 用得到的示例
pnpm workspaces 的范围定义在根目录:
packages:
- 'apps/*'
- 'packages/*'
- 'bench/*'
- 'crates/*/js'
- 'turbopack/crates/*/js'
- 'turbopack/crates/turbopack-tests/tests/execution'
- 'turbopack/packages/*'
updateNotifier: false
publicHoistPattern:
- '*eslint*'
注意一个细节:Rust crates 的 JS 部分也被纳入 workspaces(crates/*/js)。这是因为 SWC / Turbopack 都需要"Rust 编出 .node binary + JS 包薄壳"的发布方式。
2. packages/ —— 18 个 JS 包
这是日常开发最常碰的。挑出最关键的几个:
2.1 packages/next/ —— 主包
用户 npm install next 装的就是这个。结构:
packages/next/
├── src/ ← TypeScript 源码(开发时改这里)
├── dist/ ← 编译产物(git 不追,CI / 本地 build 时生成)
├── package.json ← 发布元数据 + 大量 exports 字段
├── taskfile.js ← 自定义构建脚本(基于 taskr)
├── bin/next.ts ← CLI 入口(编译到 dist/bin/next.js)
└── compiled/ ← 预编译的第三方依赖(react / webpack / babel 等)
源码进一步分层(在第 03 讲深入):
| 子目录 | 内容 |
|---|---|
src/cli/ | CLI 入口:next-dev / next-build / next-start / next-export ... |
src/build/ | 构建管线:webpack 配置、entries、manifests、turbopack-build 等 |
src/server/ | 服务运行时:base-server、next-server、app-render、route-modules ... |
src/client/ | 浏览器运行时:app-router、segment-cache、Link、Image ... |
src/shared/ | server/client 共享代码(注意:必须无 Node API) |
src/lib/ | 顶层工具函数 |
src/compiled/ | 预打包好的第三方依赖(详见 2.5) |
src/experimental/ | 实验性能力(testmode / testing-utilities) |
2.2 packages/next-swc/ —— Rust binding 的 JS 包壳
这个包不是 Rust 源码,而是把 crates/ 里编出来的 .node binary 与少量 JS 胶水代码打成 npm 包。当你跑 pnpm install 时 npm 会拉取与你平台匹配的 @next/swc-darwin-arm64 等 platform-specific binary,由这里 require 出来。
重点:当 SWC 行为异常时,要怀疑两件事——
@next/swc-*binary 是不是与本地 Rust 源码不一致(详见 AGENTS.md "Stale Native Binary");以及crates/里的 transform 逻辑。
2.3 packages/create-next-app/ —— 脚手架
npx create-next-app 命令的实现。源码独立,与主包没有强耦合。当用户报"create-next-app 模板有问题"时改这里。
2.4 其他用户可见的包
| 包名 | 作用 | 用户感知度 |
|---|---|---|
font | next/font 的实现(Google Fonts / Local Fonts) | 高 |
eslint-plugin-next / eslint-config-next | Next.js 专属 ESLint 规则 | 中 |
next-mdx | @next/mdx MDX 集成 | 中 |
third-parties | next/third-parties(Google Analytics、YouTube 等) | 中 |
next-env | @next/env:.env* 文件加载 | 低(被内部 require) |
next-codemod | npx @next/codemod:版本升级 codemod | 升级时高 |
next-bundle-analyzer | @next/bundle-analyzer:包体分析 | 调优时高 |
next-routing | 实验性路由能力 | 极低 |
next-rspack | 切换到 Rspack(webpack 5 兼容 Rust 替代)的适配层 | 实验 |
next-plugin-storybook | Storybook 集成 | 中 |
next-playwright | Playwright 配置预设 | 低 |
next-polyfill-* | 老浏览器 polyfill | 自动注入 |
react-refresh-utils | Fast Refresh 工具 | 内部使用 |
eslint-plugin-internal | 仅本仓库内部使用的 ESLint 规则 | 极低 |
2.5 packages/next/src/compiled/ —— 为什么有这么多预编译依赖
打开这个目录你会看到 react、react-dom、react-server-dom-webpack、webpack、babel、acorn、amphtml、debug……上百个常见库。
这不是 vendoring,是**"打包后再发布"**:所有这些库都已经 bundle 成单文件(CJS)并固化在 npm 包里。原因有 4 个:
- 避免 peerDeps 地狱:用户的
react版本不会影响 Next.js 内部用的react。 - 冻结行为:Next.js 自己依赖的
webpack/babel行为不会因 npm 更新而漂移。 - 缩小安装体积:用户的
node_modules/next/是一个相对完整的"自给自足"产物。 - 支持 React vendoring(关键):Next.js 同时打了 3 个 React channel(experimental、rc、stable),可在 server 与 client 之间切换。详见第 17 讲与
.claude/skills/react-vendoring/SKILL.md。
构建逻辑在主包的 taskfile.js 里,搜索 copy_vendor_react / compile_* 任务。
3. crates/ —— Next.js 自己的 Rust 代码
crates/
├── next-core/ ← 核心:page loader、entry 生成、module rules
├── next-api/ ← 给 Node JS 调用的 API 桥
├── next-build/ ← Turbopack-based next build
├── next-build-test/ ← next-build 的内部测试
├── next-custom-transforms/ ← Next 专属 SWC transforms(next/dynamic 处理等)
├── next-code-frame/ ← 错误码框(生成精美 error frame)
├── next-error-code-swc-plugin/ ← 标注内部错误码的 SWC 插件
├── next-napi-bindings/ ← N-API:Rust ↔ Node.js 桥接
├── next-taskless/ ← 构建任务调度
└── wasm/ ← WebAssembly 编译目标
关键关系:
crates/next-core/是 Turbopack 在 Next.js 这一侧的"使用层"——它告诉 Turbopack 怎么把app/page.tsx编译成可运行的代码。crates/next-custom-transforms/是给 SWC 用的(适用于 webpack 也适用于 Turbopack):每个 transform 是一个 SWC visitor,比如把import dynamic from 'next/dynamic'处理成正确的 chunk 信息。
普通业务开发不会改 Rust,但当你看到 'use client' / 'use server' 行为异常、next/dynamic 编译不正确时,要会查 crates/next-custom-transforms/。
4. turbopack/ —— git subtree
turbopack/
├── crates/ ← Turbopack 主体(50+ crates)
├── packages/ ← Turbopack 的 JS 工具
└── README.md
历史上 Turbopack 来自 vercel/turborepo 仓库,2024 年被 git subtree 合并到 next.js。它不是 npm dependency,而是源码内嵌——所以你修改 turbopack/ 里的 Rust 代码会影响 Next.js 构建。
注意几个关键 crate:
| Crate | 作用 |
|---|---|
turbopack-core | 核心抽象:Asset / Module / Chunk |
turbopack-ecmascript | JS / TS 处理(依赖 SWC) |
turbopack-ecmascript-runtime | 运行时 JS 模板(在 dev/HMR 时注入到浏览器) |
turbopack-css | CSS 处理 |
turbopack-dev-server | dev mode 的 HTTP server |
turbopack-node | Node.js 端 SSR 容器 |
turbo-tasks | 增量计算框架(Turbopack 的"灵魂") |
诊断提示:HMR 行为异常、
'use client'边界不对时,先看turbopack/crates/turbopack-ecmascript/;运行时报"模块未定义"时看turbopack-ecmascript-runtime/js/src/。
5. apps/、bench/、test/、examples/、docs/
| 目录 | 你会改这里吗 | 何时改 |
|---|---|---|
apps/ | 偶尔 | 给内部 dogfooding 加个示例 |
bench/ | 偶尔 | 写性能基准(如 bench:render-pipeline) |
test/ | 频繁 | 任何源码改动都要加测试 |
examples/ | 偶尔 | 给社区贡献新示例 |
docs/ | 偶尔 | 改了用户 API 后必须同步 |
6. 三层依赖图
把上面所有信息浓缩成一张图:
┌──────────────────────────┐
│ packages/next │ ← 用户安装的 npm 包
└────────┬─────────────────┘
│ runtime require()
▼
┌──────────────────────────┐
│ packages/next/src/ │ ← TypeScript 源码(你改这里)
│ compiled/ (vendored) │
└────────┬─────────────────┘
│ build-time bindings
┌────────┴───────────┬─────────────┐
▼ ▼ ▼
┌───────────┐ ┌────────────┐ ┌────────────────┐
│ next-swc │ │ crates/ │ │ turbopack/ │
│ (.node) │◀──── │ (Rust) │◀─│ (Rust subtree)│
└───────────┘ └────────────┘ └────────────────┘
记住这层关系:
- 改 TS 行为 →
packages/next/src/,然后pnpm --filter=next build。 - 改 SWC transform 行为(如 RSC 边界处理)→
crates/next-custom-transforms/,然后pnpm build-all。 - 改 Turbopack 行为(如新文件类型支持)→
turbopack/crates/,然后pnpm build-all。 - 改 vendored 第三方(如自定义 webpack 行为)→ 修改
packages/next/taskfile.js,重新跑compile_*任务。
7. 实操:5 道"在哪里改"的小测验
请在脑中(或翻代码)回答以下问题,每题 2 分钟内:
我想给
next/image加一个新的loader配置项。改哪里?答案
packages/next/src/client/image-component.tsx+packages/next/src/server/image-optimizer.ts+ 类型定义packages/next/src/server/config-shared.ts。我发现
'use client'在 React 19 下边界处理有 bug。改哪里?答案
主要在crates/next-custom-transforms/src/transforms/react_server_components/,可能还要联动packages/next/src/build/webpack/loaders/next-flight-loader/。我想给
pnpm new-test加一个新的模板类型。改哪里?答案
turbo/generators/(turborepo gen 配置)+ 模板文件。我想新增一个 experimental flag。改哪里?
答案
3 个最少:packages/next/src/server/config-shared.ts(类型)+config-schema.ts(zod)+packages/next/src/build/define-env.ts(用户 bundle 注入)。详见.claude/skills/flags/SKILL.md与第 23 讲。我想加一行 ESLint 规则禁止用户
import 'next/legacy/image'。改哪里?答案
packages/eslint-plugin-next/lib/rules/。
8. 检验问题
packages/next/src/compiled/为什么存在?至少说出 3 个原因。packages/next-swc与crates/next-custom-transforms是同一个东西吗?关系是什么?turbopack/为什么不是 npm 依赖,而是源码内嵌?- 改
packages/next/src/server/app-render/app-render.tsx后,需要执行哪条 build 命令最快?为什么不一定要pnpm build-all?(提示:阅读 AGENTS.md 的「Rebuilding Before Running Tests」) packages/next的package.json里有那么多*.js / *.d.ts顶层文件,它们和dist/是什么关系?
9. 延伸阅读
- 仓库根
AGENTS.md的「Codebase structure」→「Monorepo Overview」。 packages/next/README.md(这是真正发布到 npm 的 README)。turbopack/README.md(Turbopack 自己的设计说明)。.claude/skills/react-vendoring/SKILL.md(深入理解compiled/react*)。
下一讲预告
第 03 讲|源码主入口与执行流:从 bin/next 一路追到 app-render.tsx,画一张完整的"请求-渲染"调用栈。