本篇文章是《从零开发一个Coding Agent》系列第二篇,主要介绍如何使用npm搭建一个monorepo项目,以及如何管理多个子项目。
什么是monorepo项目
monorepo项目是指在一个代码仓库中管理多个子项目的项目。每个子项目都是一个独立的npm包,它们之间通过npm的依赖管理机制进行协调。npm workspaces 中,本地包之间仍然通过 package.json 声明依赖;区别是安装时 npm 会把依赖链接到本地 workspace,,而不是从 npm registry 下载。 举个例子: agent包 需要引用 ai 包。
根目录已经声明:
json
{
"workspaces": ["packages/*"]
}
packages/ai/package.json:
json
{
"name": "@di-code/ai",
"version": "0.0.0"
}
在 packages/agent/package.json 中声明:
json
{
"name": "@di-code/agent",
"version": "0.0.0",
"dependencies": {
"@di-code/ai": "0.0.0"
}
}
然后从根目录安装: npm install
npm 会建立类似下面的本地链接:
text
node_modules/@di-code/ai
-> D:\pi\di-code\packages\ai
Windows 上实际可能是 junction(目录联接),但效果与符号链接一致,不会复制一份源码。 于是 agent 中可以像使用普通 npm 包一样导入:
javascript
import type { Message } from "@di-code/ai";
不要使用跨目录相对导入:
javascript
import type { Message } from "../../ai/src/types.js";
环境和目录
接下来我们就进入了项目的真正开发了,首先需要检查自己的环境是否符合要求。node版本至少为22.19.0。
新建项目目录,我这里选择名称为di-code,并初始化根 package.json:
json
{
"name": "di-code",
"version": "0.0.0",
"private": true,
"type": "module",
"workspaces": ["packages/*"],
"scripts": {
"build": "npm run build --workspace @di-code/tui && npm run build --workspace @di-code/ai && npm run build --workspace @di-code/agent && npm run build --workspace @di-code/coding-agent",
"check": "biome check . && tsc --noEmit -p tsconfig.json",
"test": "npm run test --workspaces --if-present"
},
"devDependencies": {
"@biomejs/biome": "2.3.5",
"@types/node": "22.19.19",
"typescript": "5.9.3",
"vitest": "4.1.9"
},
"engines": {
"node": ">=22.19.0"
}
}
关键字段说明:
private: true防止对根聚合包执行意外发布。type: module确立根脚本的 ESM 语义。workspaces只匹配四个直接子包,不把未来示例或临时目录意外变成 workspace。build的显式顺序对应未来依赖方向。check先运行 Biome;只有 Biome 成功才运行 TypeScript。test将测试命令分发到所有定义了testscript 的 workspace。- 所有 devDependency 都使用精确版本,不使用
^或~。
然后新建packages目录,用于存放所有的子项目。在该目录下创建ai, agent, coding-agent, tui四个子目录。 每个子目录下创建src目录,用于存放源代码。
TypeScript配置
在 根目录创建一个tsconfig.base.json文件:
json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"noEmitOnError": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"inlineSources": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": ["node"]
}
}
部分选项说明:
target决定输出 JavaScript 的语法基线;ES2022 在最低 Node 22 运行时上可用。module与moduleResolution同时使用NodeNext,让 TypeScript 按现代 Node.js 的 ESM/CJS 规则判断包边界。不要把一个改为NodeNext、另一个留成旧的node。strict是项目约束,后续不得为了消除错误而关闭。verbatimModuleSyntax要求 import/export 写法与最终模块语义一致,并促使类型导入使用import type。noEmitOnError防止类型错误时留下看似可用的构建产物。declaration和declarationMap为 workspace 公共类型生成.d.ts与映射。sourceMap和inlineSources为后续调试保留源码定位。skipLibCheck只跳过第三方声明文件内部的检查,不跳过本项目源码检查。types: ["node"]明确引入 Node.js 全局类型,避免环境中其他@types/*包意外污染全局命名空间。
然后在根目录写入TS配置文件tsconfig.json:
json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true
},
"include": ["packages/*/src/**/*.ts", "packages/*/test/**/*.ts"],
"exclude": ["**/node_modules/**", "**/dist/**"]
}
根配置的职责是全仓检查,不是构建:
include同时覆盖源码和未来测试。noEmit保证npm run check不修改工作区。dist被排除,避免检查生成文件并重复报告问题。
编写四个 workspace package.json
在四个目录 ai, agent, coding-agent, tui 下创建 package.json 文件。它们的脚本和导出形状保持一致,名称和描述不同。
packages/ai/package.json
json
{
"name": "@di-code/ai",
"version": "0.0.0",
"description": "Provider-neutral AI contracts and adapters for di-code",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"test": "vitest run --passWithNoTests"
},
"engines": {
"node": ">=22.19.0"
}
}
packages/agent/package.json
json
{
"name": "@di-code/agent",
"version": "0.0.0",
"description": "Core agent state and loop for di-code",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"test": "vitest run --passWithNoTests"
},
"engines": {
"node": ">=22.19.0"
}
}
packages/coding-agent/package.json
json
{
"name": "@di-code/coding-agent",
"version": "0.0.0",
"description": "Coding agent product layer for di-code",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"test": "vitest run --passWithNoTests"
},
"engines": {
"node": ">=22.19.0"
}
}
packages/tui/package.json
json
{
"name": "@di-code/tui",
"version": "0.0.0",
"description": "Reusable terminal UI library for di-code",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"test": "vitest run --passWithNoTests"
},
"engines": {
"node": ">=22.19.0"
}
}
main指向运行时 ESM 入口。types指向 TypeScript 声明入口。exports限制包的公开入口为根入口,防止后续调用者依赖内部文件路径。
编写 workspace 构建配置
在以下四个位置创建相同内容的 tsconfig.build.json:
text
packages\ai\tsconfig.build.json
packages\agent\tsconfig.build.json
packages\coding-agent\tsconfig.build.json
packages\tui\tsconfig.build.json
内容均为:
json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "test"]
}
extends让四个包继承完全相同的严格模式和 ESM 语义。rootDir告诉编译器源码相对根为src。outDir使输出留在各自包内,不把不同包混到根dist。- build 只包含
src,测试不进入发布产物。
入口创建
在以下四个文件中各写一行:
text
packages\ai\src\index.ts
packages\agent\src\index.ts
packages\coding-agent\src\index.ts
packages\tui\src\index.ts
内容:
ts
export {};
配置 Biome 格式化工具
Biome 是面向 JavaScript、TypeScript、JSON 等文件的代码质量工具,简单来说就是ESLint + Prettier。
在 di-code\biome.json 写入:
json
{
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
"files": {
"includes": [
"package.json",
"biome.json",
"tsconfig*.json",
"packages/*/package.json",
"packages/*/tsconfig*.json",
"packages/*/src/**/*.ts",
"packages/*/test/**/*.ts"
]
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "tab",
"lineWidth": 120
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
编写 .gitignore
在根目录下创建 .gitignore 写入:
gitignore
# Dependencies
node_modules/
# Build and test output
dist/
coverage/
*.tsbuildinfo
# Logs
*.log
npm-debug.log*
# Environment and credentials
.env
.env.*
!.env.example
# Editors and operating systems
.vscode/
.idea/
.DS_Store
Thumbs.db
这些都不需要提交代码仓库
安装依赖与构建
到这里我们基本已经完成了基础的 monorepo 配置。 接下来,我们可以开始安装依赖并构建项目。
bash
npm install
npm run build
构建完成之后我们就能看到每个包下面都有一个 dist 目录,里面包含了编译后的代码。
最后执行一下检查,确保代码质量。
bash
npm run check
如果 Biome 只报告格式差异,可以执行
powershell
npx biome check --write package.json tsconfig.base.json tsconfig.json biome.json packages
进行修复,然后再执行 npm run check 确保格式符合要求。
到这里我们已经完成了基础的 monorepo 配置。本章节git分支Monorepo 初始化: