跳转到内容

Flowise 的 Monorepo 架构:pnpm + Turbo + 模块化节点池

学习目标

  • 理解 Flowise 的 Monorepo 结构:6 个子包的职责与依赖关系
  • 掌握 pnpm workspace + Turbo 的任务编排机制
  • 了解 NodesPool 如何在启动时动态发现和加载节点

前置知识

本章为项目特定的工程实践,无外部前置知识要求。


项目实践

Monorepo 结构

Flowise 使用 pnpm workspaces 管理 6 个子包:

packages/
├── server/ # Express.js 后端,API 逻辑、数据库、队列
├── ui/ # React 前端,管理界面
├── components/ # 节点库:LLM、工具、记忆、向量存储等
├── agentflow/ # React Flow 画布编辑器
├── observe/ # 可观测性模块
└── api-documentation/ # Swagger API 文档

依赖关系

server → components (flowise-components)
server → ui (flowise-ui)
ui → agentflow (React Flow 组件)

pnpm Workspace 配置

pnpm-workspace.yaml 定义:

packages:
- 'packages/*'

所有子包通过 workspace:* 引用彼此:

{
"dependencies": {
"flowise-components": "workspace:^",
"flowise-ui": "workspace:^"
}
}

Turbo 任务编排

turbo.json 定义构建和开发任务:

任务依赖说明
build^build先构建依赖,再构建自己
dev开发模式,热重载
start^build先构建再启动

NodesPool 动态加载

NodesPool 类是 Flowise 节点系统的核心:

class NodesPool {
componentNodes: IComponentNodes = {} // 所有已加载的节点
componentCredentials: IComponentCredentials = {} // 所有认证信息
}

节点加载流程

关键代码

private async initializeNodes() {
const packagePath = getNodeModulesPackagePath('flowise-components')
const nodesPath = path.join(packagePath, 'dist', 'nodes')
const nodes = await this.loadNodesFromDir(nodesPath)
Object.assign(this.componentNodes, nodes)
}
async loadNodesFromDir(dir: string): Promise<IComponentNodes> {
const disabled_nodes = process.env.DISABLED_NODES?.split(',') ?? []
const nodeFiles = await this.getFiles(dir)
await Promise.all(nodeFiles.map(async (file) => {
if (file.endsWith('.js')) {
const nodeModule = await require(file)
if (nodeModule.nodeClass) {
const instance = new nodeModule.nodeClass()
instance.filePath = file
// 图标路径处理...
if (!disabled_nodes.includes(instance.name)) {
nodes[instance.name] = instance
}
}
}
}))
}

Feature Flag:节点禁用机制

Flowise 支持通过环境变量禁用特定节点:

Terminal window
DISABLED_NODES=nodeA,nodeB,nodeC

同时支持 appConfig.showCommunityNodes 控制是否显示社区贡献的节点(通过节点是否有 author 字段判断)。


问题与规避

启动时间 vs 运行时性能

问题:NodesPool 在启动时全量加载所有节点(数百个 .js 文件),启动时间较长。

对策

  • 使用 DISABLED_NODES 环境变量禁用不需要的节点,减少加载数量
  • 节点加载是 I/O 密集型的 require() 操作,可通过 SSD 提升速度
  • 生产环境建议预热启动一次后保持进程常驻

动态 require 的安全风险

问题:从文件系统直接 require() 用户可编辑的 .js 文件,存在代码注入风险。

对策

  • 节点文件来自 flowise-components 包(npm 安装),非用户上传
  • 社区节点通过 author 字段标识,管理员可选择是否加载
  • 用户自定义代码通过 vm2 NodeVM 沙箱执行(见”沙箱安全”章节)

设计取舍

为什么选 pnpm 而非 npm/yarn?

方案优势代价
pnpm节省磁盘空间(硬链接)、严格依赖隔离与某些工具链兼容性差
npm生态最广、开箱即用重复安装依赖、phantom dependencies
yarn介于两者之间社区分流

Flowise 选择 pnpm 的核心原因:

  • Monorepo 中多个子包共享相同依赖(如 @langchain/core),pnpm 的 content-addressable store 节省大量磁盘空间
  • 严格依赖隔离避免了 phantom dependencies(npm 可以 require 未声明的依赖)

文件扫描 vs 注册表

方案优势代价
文件扫描 + require零配置,新节点只需放在目录下启动时全量扫描
注册表精确控制加载哪些节点需要手动维护注册表

Flowise 选择文件扫描,因为:

  • 降低新节点开发门槛:只需导出 nodeClass 即可自动注册
  • 社区贡献友好:不需要修改核心代码
  • 代价是启动时间,但可以通过禁用不需要的节点缓解

参考来源