从 0 到 1:AI Todos 项目 Day1 实战——用 pnpm + Turbo 搭建可迭代的 Monorepo 基线

简介: 记录AI待办(AiTodos)Monorepo项目的首日基础工程搭建全过程,涵盖pnpm工作区配置、目录骨架初始化、Turbo任务编排、TypeScript统一配置、Vite/Fastify应用初始化、跨包共享(shared/api-sdk/store)及标准化脚本建设,完成可运行、可检查、可扩展的现代化前端工程基线。

Day 1 基础工程搭建(Step 1-19)执行笔记

微信图片_20260501114927_1316_367.png

Step 1:修正 pnpm-workspace.yaml

  • 将 workspace 配置修正为:
  • packages:
  • apps/*
  • packages/*
  • 修复了初始版本中缺少 packages: 根字段、packages/*. 多余 . 的问题。
  • 结果:pnpm 能正确识别 appspackages 下的工作区包。

packages:

 - apps/*

 - packages/*

Step 2:创建 Monorepo 目录骨架

  • 创建了核心目录结构:
  • apps/web
  • apps/admin
  • apps/server
  • packages/shared
  • packages/api-sdk
  • packages/store
  • 结果:Monorepo 目录基础成型,可继续分包初始化。


Step 3:创建根目录 .gitignore

  • 添加了常见忽略项:node_modulesdistbuild.turbocoverage.env**.log
  • 结果:避免依赖与构建产物进入版本库,保持仓库整洁。

node_modules

dist

build

.turbo

coverage

.env

.env.*

*.log

Step 4:改造根 package.json

  • 设置 private: true,明确这是 Monorepo 根工程。
  • 配置统一脚本:devbuildlinttype-checkformat
  • dev 脚本更新为 turbo run dev(去除已弃用的 --parallel)。
  • 保留 packageManager: pnpm@10.31.0,统一团队工具版本。

{

 "name": "ai-todos",

 "private": true,

 "version": "1.0.0",

 "scripts": {

   "dev": "turbo run dev",

   "build": "turbo run build",

   "lint": "turbo run lint",

   "type-check": "turbo run type-check",

   "format": "prettier . --write"

 },

 "keywords": [],

 "author": "",

 "license": "ISC",

 "packageManager": "pnpm@10.31.0",

 "devDependencies": {

   "eslint": "^10.2.1",

   "prettier": "^3.8.3",

   "turbo": "^2.9.6",

   "typescript": "^6.0.3"

 }

}

Step 5:安装根开发依赖

  • 在 workspace 根安装开发依赖:turbotypescripteslintprettier
  • 使用 pnpm add -Dw ... 显式声明安装到 workspace root。
  • 结果:依赖写入根 devDependencies,并生成/更新 pnpm-lock.yaml

pnpm add -Dw turbo typescript eslint prettier

Step 6:创建 turbo.json

  • 配置了基础任务编排:devbuildlinttype-check
  • 关键规则:
  • dev 为常驻任务(persistent)且不缓存(cache: false)。
  • build 依赖上游 ^build 并声明输出目录(dist/**build/**)。
  • linttype-check 依赖上游同名任务,保证执行顺序。
  • 因 IDE 信任策略,移除了 $schema,不影响 turbo 实际执行。

{

   "tasks": {

       "dev": {

           "cache": false,

           "persistent": true

       },

       "build": {

           "dependsOn": [

               "^build"

           ],

           "outputs": [

               "dist/**",

               "build/**"

           ]

       },

       "lint": {

           "dependsOn": [

               "^lint"

           ]

       },

       "type-check": {

           "dependsOn": [

               "^type-check"

           ]

       }

   }

}

Step 7:准备 TypeScript 根配置

  • 新建 tsconfig.base.json 作为全项目共享 TS 配置。
  • 统一了关键编译选项:strictmodulemoduleResolutionresolveJsonModuleskipLibCheck 等。
  • 初始配置中包含 baseUrl,后续在 TS 6 下触发弃用报错后已移除,恢复 type-check 正常通过。
  • 结果:apps/*packages/* 可以通过 extends 复用同一套 TS 规范。

{

   "compilerOptions": {

       "target": "ES2022",

       "lib": [

           "ES2022",

           "DOM",

           "DOM.Iterable"

       ],

       "module": "ESNext",

       "moduleResolution": "Bundler",

       "strict": true,

       "skipLibCheck": true,

       "resolveJsonModule": true,

       "esModuleInterop": true,

       "isolatedModules": true,

       "noUncheckedIndexedAccess": true

   }

}

Step 8:初始化 apps/web

  • 完成 web 最小可运行工程:package.jsontsconfig.jsonvite.config.tsindex.htmlsrc/App.tsxsrc/main.tsx
  • 脚本包含:devbuildtype-check
  • 接入依赖:reactreact-domvite@vitejs/plugin-react@types/react@types/react-dom
  • 结果:@aitodos/web 可独立启动并通过类型检查。

package.json:

{

   "name": "@aitodos/web",

   "private": true,

   "version": "1.0.0",

   "scripts": {

       "dev": "vite",

       "build": "vite build",

       "type-check": "tsc --noEmit"

   },

   "dependencies": {

       "react": "^19.2.5",

       "react-dom": "^19.2.5"

   },

   "devDependencies": {

       "@types/react": "^19.2.14",

       "@types/react-dom": "^19.2.3",

       "@vitejs/plugin-react": "^6.0.1",

       "vite": "^8.0.10"

   }

}

tsconfig.json:

{

   "extends": "../../tsconfig.base.json",

   "compilerOptions": {

       "jsx": "react-jsx",

       "types": [

           "vite/client"

       ],

       "noEmit": true

   },

   "include": [

       "src",

       "vite.config.ts"

   ]

}

vite.config.ts:

import { defineConfig } from "vite";

import react from "@vitejs/plugin-react";

export default defineConfig({

   plugins: [react()]

});

index.html:

<!doctype html>

<html lang="en">

<head>

   <meta charset="UTF-8" />

   <meta name="viewport" content="width=device-width, initial-scale=1.0" />

   <title>AiTodos Web</title>

</head>

<body>

   <div id="root"></div>

   <script type="module" src="/src/main.tsx"></script>

</body>

</html>

src/App.tsx:

export default function App() {

   return <h1>AiTods Web is running</h1>;

}

src/main.tsx:

import React from "react";

import ReactDOM from "react-dom/client";

import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(

   <React.StrictMode>

       <App />

   </React.StrictMode>

);

Step 9:初始化 apps/admin

  • 完成 admin 最小可运行工程(与 web 同构):配置文件与入口文件已齐全。
  • 脚本包含:devbuildtype-check
  • 接入依赖:React + Vite 相关依赖。
  • 结果:@aitodos/admin 可独立启动并通过类型检查。

配置参考apps/web,其中:package.json的name、index.html的title、src/App.tsx要按需修改

Step 10:初始化 apps/server

  • 完成 server 最小 Fastify 服务:package.jsontsconfig.jsontsconfig.build.jsonsrc/index.ts
  • 脚本包含:devtsx watch)、buildstarttype-check
  • 提供 /health 健康检查接口,默认监听 3000
  • 结果:@aitodos/server 可启动并返回健康检查响应。

package.json:

{

   "name": "@aitodos/server",

   "private": true,

   "version": "1.0.0",

   "scripts": {

       "dev": "tsx watch src/index.ts",

       "build": "tsc -p tsconfig.build.json",

       "start": "node dist/index.js",

       "type-check": "tsc --noEmit"

   },

   "dependencies": {

       "@aitodos/shared": "workspace:*",

       "fastify": "^5.8.5"

   },

   "devDependencies": {

       "@types/node": "^24.7.2",

       "tsx": "^4.20.6"

   }

}

tsconfig.json:

{

   "extends": "../../tsconfig.base.json",

   "compilerOptions": {

       "moduleResolution": "NodeNext",

       "module": "NodeNext",

       "types": [

           "node"

       ],

       "noEmit": true

   },

   "include": [

       "src"

   ]

}

tsconfig.build.json:

{

   "extends": "../../tsconfig.base.json",

   "compilerOptions": {

       "moduleResolution": "NodeNext",

       "module": "NodeNext",

       "types": [

           "node"

       ],

       "noEmit": true

   },

   "include": [

       "src"

   ]

}

src/index.ts:

import Fastify from "fastify";

const app = Fastify({ logger: true });

const port = Number(process.env.PORT ?? 3000);

app.get("/health", async () => {

   return { ok: true, service: "server"};

});

const start = async () => {

   try {

       await app.listen({ port, host: "0.0.0.0" });

       app.log.info(`Server running at http://localhost:${port}`);

   } catch (error) {

       app.log.error(error);

       process.exit(1);

   }

};

start();

Step 11:初始化 packages/shared 并完成跨包引用

  • 创建共享包文件:package.jsontsconfig.jsontsconfig.build.jsonsrc/index.ts
  • 导出共享内容:APP_NAMETodoStatusTodoItemnormalizeTodoTitle
  • webserver 中分别添加依赖:@aitodos/shared: workspace:*
  • 结果:webserver 可成功 import 共享包。

package.json:

{

   "name": "@aitodos/shared",

   "private": true,

   "version": "1.0.0",

   "type": "module",

   "exports": {

       ".": "./src/index.ts"

   },

   "scripts": {

       "build": "tsc -p tsconfig.build.json",

       "type-check": "tsc --noEmit"

   }

}

tsconfig.json:

{

 "extends": "../../tsconfig.base.json",

 "compilerOptions": {

   "noEmit": true

 },

 "include": ["src"]

}

tsconfig.build.json:

{

   "extends": "./tsconfig.json",

   "compilerOptions": {

       "noEmit": false,

       "outDir": "dist",

       "declaration": true,

       "declarationMap": true,

       "sourceMap": true

   },

   "include": [

       "src"

   ]

}

src/index.ts:

export const APP_NAME = "AiTodos";

export type TodoStatus = "todo" | "doing" | "done";

export interface TodoItem {

   id: string;

   title: string;

   status: TodoStatus;

}

export const normalizeTodoTitle = (title: string) => title.trim();

在shell中执行:

pnpm add @aitodos/shared@workspace:* --filter @aitodos/web

pnpm add @aitodos/shared@workspace:* --filter @aitodos/admin

web/src/App.tsx:

import { APP_NAME } from "@aitodos/shared";

import { createApiClient } from "@aitodos/api-sdk";

const api = createApiClient("http://localhost:3000");

void api;

export default function App() {

   return <h1>{APP_NAME} Web is running</h1>;

}

server/src/index.ts:

import Fastify from "fastify";

import { APP_NAME } from "@aitodos/shared";

const app = Fastify({ logger: true });

const port = Number(process.env.PORT ?? 3000);

app.get("/health", async () => {

   return { ok: true, service: "server", app: APP_NAME };

});

const start = async () => {

   try {

       await app.listen({ port, host: "0.0.0.0" });

       app.log.info(`Server running at http://localhost:${port}`);

   } catch (error) {

       app.log.error(error);

       process.exit(1);

   }

};

start();

Step 12:初始化 packages/api-sdk

  • 创建 SDK 包文件:package.jsontsconfig.jsontsconfig.build.jsonsrc/index.ts
  • 实现最小客户端:createApiClient(baseUrl) + getHealth()
  • webadmin 中添加依赖:@aitodos/api-sdk: workspace:*
  • 结果:前端应用可引用 api-sdk,为后续接口联调预留入口。

package.json:

{

   "name": "@aitodos/api-sdk",

   "private": true,

   "version": "1.0.0",

   "type": "module",

   "exports": {

       ".": "./src/index.ts"

   },

   "scripts": {

       "build": "tsc -p tsconfig.build.json",

       "type-check": "tsc --noEmit"

   }

}

tsconfig.json:

{

   "extends": "../../tsconfig.base.json",

   "compilerOptions": {

       "noEmit": true

   },

   "include": [

       "src"

   ]

}

tsconfig.build.json:

{

   "extends": "./tsconfig.json",

   "compilerOptions": {

       "noEmit": false,

       "outDir": "dist",

       "declaration": true,

       "declarationMap": true,

       "sourceMap": true

   },

   "include": [

       "src"

   ]

}

src/index.ts:

export interface HealthResponse {

 ok: boolean;

 service: string;

 app?: string;

}

export const createApiClient = (baseUrl: string) => {

 const getHealth = async (): Promise<HealthResponse> => {

   const res = await fetch(`${baseUrl}/health`);

   if (!res.ok) {

     throw new Error(`Health request failed: ${res.status}`);

   }

   return res.json() as Promise<HealthResponse>;

 };

 return { getHealth };

};

在根目录shell执行:

pnpm add @aitodos/shared@workspace:* --filter @aitodos/web

pnpm add @aitodos/shared@workspace:* --filter @aitodos/admin

Step 13:初始化 packages/store

  • 创建 store 包文件:package.jsontsconfig.jsontsconfig.build.jsonsrc/index.ts
  • 提供最小状态结构:AppStatecreateInitialState()
  • webadmin 中添加依赖:@aitodos/store: workspace:*
  • 结果:状态层共享包占位完成,可在 Day 2 继续扩展。

package.json:

{

   "name": "@aitodos/api-sdk",

   "private": true,

   "version": "1.0.0",

   "type": "module",

   "exports": {

       ".": "./src/index.ts"

   },

   "scripts": {

       "build": "tsc -p tsconfig.build.json",

       "type-check": "tsc --noEmit"

   }

}

tsconfig.json:

{

   "extends": "../../tsconfig.base.json",

   "compilerOptions": {

       "noEmit": true

   },

   "include": [

       "src"

   ]

}

tsconfig.build.json:

{

   "extends": "./tsconfig.json",

   "compilerOptions": {

       "noEmit": false,

       "outDir": "dist",

       "declaration": true,

       "declarationMap": true,

       "sourceMap": true

   },

   "include": [

       "src"

   ]

}

src/index.ts:

export interface AppState {

 keyword: string;

}

export const createInitialState = (): AppState => ({

 keyword: ""

});

在根目录shell执行:

pnpm add @aitodos/store@workspace:* --filter @aitodos/web

pnpm add @aitodos/store@workspace:* --filter @aitodos/admin

Step 14:统一 workspace 包配置

  • 统一各工作区 name 命名为 scoped 包名(@aitodos/*)。
  • 应用包与基础包的 scripts 已按职责拆分(dev/build/type-check)。
  • 使用 workspace:* 明确内部依赖关系,避免误拉远程同名包。
  • 结果:各包职责与依赖边界清晰。

Step 15:统一 TypeScript 检查

  • 对关键应用执行 type-check@aitodos/web@aitodos/admin(均通过)。
  • 遇到 TS 6 对 baseUrl 的弃用报错后完成修正并复测通过。
  • 结果:Day 1 核心工作区具备可持续类型校验能力。

Step 16:补齐格式化配置

  • 新建根 .prettierrc,统一基础格式规则(分号、引号、尾逗号)。
  • 新建根 .prettierignore,忽略依赖与构建目录(node_modulesdistbuild.turbopnpm-lock.yaml)。
  • 结果:格式化策略可在全仓库复用。

Step 17:确认根任务调度可用

  • 根脚本 dev/build/lint/type-check/format 已配置并可由 turbo 调度。
  • pnpm dev 可识别工作区任务并并发拉起应用。
  • 结果:Monorepo 统一命令入口可用。

Step 18:安装与运行验收

  • 执行 pnpm install,确认 workspace 依赖解析正常。
  • 执行应用启动与检查命令:web/admin/server 均可启动,type-check 通过。
  • 运行中出现 esbuild build scripts 提示,为 pnpm 安全提示,不阻塞当前 Day 1。
  • 结果:完成 Day 1 运行态验收。

Step 19:首次提交与里程碑确认

  • 已完成首次提交:chore: initialize monorepo day1 foundation
  • 当前分支:masterHEAD 指向 Day 1 初始化提交。
  • 结果:形成可回溯的 Day 1 基线,便于 Day 2 继续迭代。

配置说明速记

  • packageManager: "pnpm@10.31.0"
  • 指定项目建议使用的包管理器与版本,确保团队和 CI 行为一致。
  • 根脚本含义
  • dev: turbo run dev,并发拉起各 workspace 的开发服务。
  • build: turbo run build,按依赖拓扑执行构建。
  • lint: turbo run lint,执行规范检查。
  • type-check: turbo run type-check,执行类型检查(通常不产物)。
  • format: prettier . --write,格式化并写回文件。
  • pnpm add vs pnpm i
  • add 用于新增依赖并写入 package.json
  • i/install 用于按现有清单安装依赖。
  • pnpm add -Dw ...
  • -D 表示安装到 devDependencies
  • -w 表示显式安装到 workspace root(避免 ERR_PNPM_ADDING_TO_ROOT)。
  • turbo.json 关键项
  • persistent: true:常驻任务(如 dev server)。
  • cache: false:开发任务不走缓存。
  • dependsOn: ["^build"]:先执行上游依赖包同名任务。
  • outputs:声明构建产物路径,供 Turbo 缓存复用。
  • tsconfig 关键项
  • include: ["src"]:限定 TS 检查/编译范围在源码目录。
  • noEmit: true:仅类型检查,不输出编译文件。
  • noEmit: false:允许输出构建产物(常用于 tsconfig.build.json)。
  • declaration: true:生成 .d.ts 类型声明。
  • declarationMap: true:生成类型声明映射,便于 IDE 跳转源码。
  • sourceMap: true:生成源码映射,便于调试定位 TS 源码行号。
  • workspace:*(如 "@aitodos/shared": "workspace:*"
  • 表示依赖当前 monorepo 内的本地包,而不是远程 registry。
  • 可确保跨包联动开发时始终引用本地最新代码。
  • scoped 包名里的 @
  • @aitodos/web@aitodos 是命名空间(scope),用于组织包并避免重名。
  • @types/* 是什么
  • @types/react,是社区提供的 TypeScript 类型声明包,通常放在 devDependencies
  • apps/web/node_modules 为什么会出现
  • 在 pnpm workspace 下属于正常现象,通常是链接结构,不代表依赖安装错误。
  • vite 启动时偶发 Exit code: 1
  • 常见于 IDE/终端中断常驻进程;若页面可访问且服务日志正常,一般不属于配置错误。
相关文章
|
19天前
|
人工智能 缓存 前端开发
Day4-5:Web 双端适配与 Admin 系统全栈落地实录
本文档整合了 Day 4 与 Day 5 的开发进展,核心涵盖 Web 端响应式 UI 复现、云端资讯 API 接入,以及 Admin 管理系统的架构设计与模块化开发步骤,打通了从用户体验到后台管理的完整链路。
|
22天前
|
人工智能 监控 安全
多模态AI(图像+文本)该怎么测试?不是把图片丢给模型这么简单
本文系统阐述多模态AI测试新范式:突破传统文本测试局限,聚焦图像理解、图文对齐、跨模态推理、幻觉防控、安全注入与鲁棒性验证六大核心维度,提出分层模型、六维测试矩阵及自动化评测体系,强调“证据链”验证——答案必须可追溯至图片真实信息。
|
21天前
|
人工智能 安全 5G
我瞎填的申请“入选”了小米百万亿 Token 计划,顶级营销就是让你觉得“只有你入选了”
彪哥揭秘小米“百万亿Token计划”营销玄机:填个邮箱即“入选”,实为全自动群发邮件。它用“申请—等待—被选中”仪式感,把普通福利包装成荣誉激励,精准拿捏人性——你明知是套路,却仍为那句“你被选中了”心头一热。这才是高级的AI驱动营销。(239字)
431 0
我瞎填的申请“入选”了小米百万亿 Token 计划,顶级营销就是让你觉得“只有你入选了”
|
22天前
|
人工智能 安全 API
Claude Cowork 支持第三方模型接入 开放而不开源
Claude Cowork 正式支持第三方推理平台接入(如Bedrock、Vertex AI、Azure Foundry及兼容/v1/messages的LLM网关),实现工具层与模型层解耦。用户可自由配置国产模型(如Qwen、GLM、DeepSeek等),降低使用门槛与成本,同时保留桌面端Agent工作流、MCP、插件及本地文件访问等核心体验——开放接口,不开放入口。
1228 7
Claude Cowork 支持第三方模型接入 开放而不开源
|
22天前
|
SQL 存储 关系型数据库
MySQL介绍:零基础入门,读懂这款主流关系型数据库
MySQL是全球最流行的开源关系型数据库,由瑞典MySQL AB公司开发,现属Oracle旗下。它基于SQL语言,以表格组织数据,支持事务(ACID)、高并发与多平台部署,免费易用、性能稳定,广泛应用于网站、企业系统及移动应用等场景。
359 3
|
1月前
|
大数据 PHP
5个提升开发效率的PHP技巧
5个提升开发效率的PHP技巧
345 143
|
29天前
|
机器学习/深度学习 缓存 测试技术
DeepSeek-V4开源:百万上下文,Agent能力比肩顶级闭源模型
DeepSeek-V4正式开源!含V4-Pro(1.6T参数)与V4-Flash(284B参数)双版本,均支持百万token上下文。首创混合注意力架构,Agent能力、世界知识与推理性能全面领先开源模型,数学/代码评测比肩顶级闭源模型。
3471 10
|
1月前
|
开发者 黑灰产治理
阿里云开发者社区积分细则
阿里云开发者社区,积分规则、领取、过期等相关说明
|
1月前
|
安全 数据建模 测试技术
阿里云SSL证书免费版与付费版差异解析,以及免费SSL证书申请流程
阿里云提供免费与付费SSL证书,满足不同场景HTTPS数据加密需求。免费证书适用于个人站点、测试环境,而政府机构、电商平台等建议选用OV/EV付费证书,以获得更高安全保障。阿里云SSL证书支持单域名、通配符等类型,并定期推出促销活动,新用户可享6折优惠起,还有免费试用和HTTPS加速网关等增值服务。企业可根据需求科学选配,构建安全可信的在线业务环境。

热门文章

最新文章