跳到主要内容

02. Monorepo 与启动:pnpm、Turbo、DI 和 CLI

2.1 根目录不是应用,而是构建控制面

package.jsonnamen8n-monorepo,版本为 2.33.0,包管理器固定为 pnpm 10.32.1,Node 要求 >=22.22。根脚本把复杂度交给 Turbo:

  • build:所有包按依赖拓扑构建。
  • dev:并行启动后端、前端和开发包,并排除不适合热启动的包。
  • testtypechecklint:按照 Turbo pipeline 运行。
  • build:n8nbuild:docker:把包构建结果聚合成可运行发行物。

根目录通过 pnpm-workspace.yaml 声明 workspace,通过各包的 package.json 维护边界。阅读时,先看包的 namedependenciesexports 和 scripts,再看源码。

2.2 包的职责分区

关键包可以这样记:

主要问题
packages/workflow工作流是什么,数据如何表示,节点能看到什么
packages/core图如何执行,节点如何得到上下文,触发器如何管理
packages/cli服务器如何启动,API、认证、数据库、队列如何组合
packages/nodes-base官方集成如何以节点描述和执行函数出现
packages/frontend/editor-ui编辑器如何呈现图、参数和 execution 状态
packages/@n8n/dbEntity、Repository、迁移和数据库连接
packages/@n8n/task-runner*用户代码和外部任务的隔离执行

2.3 启动链路

CLI 命令的典型路径是:

packages/cli/bin/n8n
→ commands/start.ts
→ server.ts / abstract-server.ts
→ DI container 注册服务
→ 数据库、节点、凭证、Webhook、Push、controller 初始化
→ HTTP server listen

@n8n/di 提供 @Service() 等装配基础。服务类把依赖写在构造函数里,由容器在启动阶段解析。这种构造函数注入让 WorkflowRunnerCredentialsHelperExecutionService 等大型服务可被单元测试替换依赖;代价是启动图很大,新增服务时必须理解模块加载顺序和生命周期。

相关源码:

2.4 启动后的三类注册

启动阶段不只是监听端口,还要完成三类注册:

  1. 节点注册:扫描 nodes-base、LangChain 和社区包,建立 node type → class/description 的索引。
  2. 路由注册:controller registry 将控制器的方法挂到 REST、Webhook、OAuth、MCP 等路由。
  3. 运行时注册:激活工作流的 trigger、poller、scheduler,worker 的队列消费者,以及 Push/SSE 通道。

这解释了为什么“只启动一个 HTTP server”不能还原 n8n:真正可工作的实例还需要数据库、节点加载器、密钥配置和运行时注册表。

2.5 设计取舍

Turbo + workspace 解决的是源码规模,不是运行时隔离。包在源码层分离,最终仍可能被 n8n CLI 聚合进同一个 Node 进程。DI 解决的是依赖可替换性,不会自动解决模块边界;边界仍由 package dependencies、ESLint/boundaries 检查和代码审查维护。

开发模式为什么要过滤包? 前端、后端、设计系统、task runner 的 watch 模式对端口、资源和依赖要求不同。根脚本通过 --filter 把常用开发面收窄,避免一次启动整个 monorepo。

2.6 读源码技巧

先从 package graph 画出“谁能依赖谁”,再从 commands/start.ts 反向追服务实例。遇到一个服务时,先看构造函数依赖和 init/shutdown 方法,再看业务方法;这比直接从几千行 service 中间跳转更容易恢复生命周期。