
pnpm-workspace.yamlpackages:apps/*packages/*packages: 根字段、packages/*. 多余 . 的问题。pnpm 能正确识别 apps 与 packages 下的工作区包。packages:
- apps/*
- packages/*apps/webapps/adminapps/serverpackages/sharedpackages/api-sdkpackages/store

.gitignorenode_modules、dist、build、.turbo、coverage、.env*、*.log。node_modules
dist
build
.turbo
coverage
.env
.env.*
*.logpackage.jsonprivate: true,明确这是 Monorepo 根工程。dev、build、lint、type-check、format。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"
}
}turbo、typescript、eslint、prettier。pnpm add -Dw ... 显式声明安装到 workspace root。devDependencies,并生成/更新 pnpm-lock.yaml。pnpm add -Dw turbo typescript eslint prettierturbo.jsondev、build、lint、type-check。dev 为常驻任务(persistent)且不缓存(cache: false)。build 依赖上游 ^build 并声明输出目录(dist/**、build/**)。lint、type-check 依赖上游同名任务,保证执行顺序。$schema,不影响 turbo 实际执行。{
"tasks": {
"dev": {
"cache": false,
"persistent": true
},
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"dist/**",
"build/**"
]
},
"lint": {
"dependsOn": [
"^lint"
]
},
"type-check": {
"dependsOn": [
"^type-check"
]
}
}
}tsconfig.base.json 作为全项目共享 TS 配置。strict、module、moduleResolution、resolveJsonModule、skipLibCheck 等。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
}
}apps/webweb 最小可运行工程:package.json、tsconfig.json、vite.config.ts、index.html、src/App.tsx、src/main.tsx。dev、build、type-check。react、react-dom、vite、@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>
);apps/adminadmin 最小可运行工程(与 web 同构):配置文件与入口文件已齐全。dev、build、type-check。@aitodos/admin 可独立启动并通过类型检查。apps/serverserver 最小 Fastify 服务:package.json、tsconfig.json、tsconfig.build.json、src/index.ts。dev(tsx watch)、build、start、type-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();packages/shared 并完成跨包引用package.json、tsconfig.json、tsconfig.build.json、src/index.ts。APP_NAME、TodoStatus、TodoItem、normalizeTodoTitle。web、server 中分别添加依赖:@aitodos/shared: workspace:*。web 与 server 可成功 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/adminweb/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();packages/api-sdkpackage.json、tsconfig.json、tsconfig.build.json、src/index.ts。createApiClient(baseUrl) + getHealth()。web、admin 中添加依赖:@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/adminpackages/storepackage.json、tsconfig.json、tsconfig.build.json、src/index.ts。AppState 与 createInitialState()。web、admin 中添加依赖:@aitodos/store: workspace:*。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/adminname 命名为 scoped 包名(@aitodos/*)。scripts 已按职责拆分(dev/build/type-check)。workspace:* 明确内部依赖关系,避免误拉远程同名包。type-check:@aitodos/web、@aitodos/admin(均通过)。baseUrl 的弃用报错后完成修正并复测通过。.prettierrc,统一基础格式规则(分号、引号、尾逗号)。.prettierignore,忽略依赖与构建目录(node_modules、dist、build、.turbo、pnpm-lock.yaml)。dev/build/lint/type-check/format 已配置并可由 turbo 调度。pnpm dev 可识别工作区任务并并发拉起应用。pnpm install,确认 workspace 依赖解析正常。web/admin/server 均可启动,type-check 通过。esbuild build scripts 提示,为 pnpm 安全提示,不阻塞当前 Day 1。chore: initialize monorepo day1 foundation。master,HEAD 指向 Day 1 初始化提交。packageManager: "pnpm@10.31.0"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 iadd 用于新增依赖并写入 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:*")@@aitodos/web 中 @aitodos 是命名空间(scope),用于组织包并避免重名。@types/* 是什么@types/react,是社区提供的 TypeScript 类型声明包,通常放在 devDependencies。apps/web/node_modules 为什么会出现vite 启动时偶发 Exit code: 1原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。