基于pnpm搭建monorepo项目

约定小结

  1. monorepo是一种代码组织结构,和最终的部署方式无关
  2. monorepo的意义在于抽离公共的内容
  3. core包要定义 exports、files、main、types的设置

执行

目录结构

目录结构 复制代码
project/
├─ pnpm-workspace.yaml          # 新增
├─ package.json                 # 新增(根,private)
├─ apps/
│  ├─ frontend-a/              # 前端 A (Vue3)
│  ├─ frontend-b/               # 前端 B (Vue3)
│  ├─ backend-a/                # 后端 A (Nest)
│  └─ backend-b/                # 后端 B (Nest)
└─ packages/
   ├─ core/                   # = 现在的 common(跨端契约:schema/类型/模板)
   ├─ ui/                       # 未来:共享 .vue 组件(仅当前端需要共享组件时才建)
   └─ server-core/              # 未来:后端共享 ts 逻辑(仅当两个后端有共用非契约逻辑时建)

创建过程一:搭建基础骨架

初始化 monorepo 骨架

bash 复制代码
mkdir project && cd project
pnpm init            # 生成根 package.json,后面再改内容
mkdir apps packages

创建 pnpm-workspace.yaml

shell 复制代码
packages:
  - "apps/*"
  - "packages/*"

修改package.json

json 复制代码
// package.json(根)
{
  "name": "packages",
  "private": true,
  "packageManager": "pnpm@9.15.0",
  "engines": { "node": ">=20" },
  "scripts": {
    "build:shared":   "pnpm --filter @repo/shared build",
    "build:front:a":  "pnpm --filter frontend build -- --mode deploy-a --outDir dist-a",
    "build:front:b":  "pnpm --filter frontend build -- --mode deploy-b --outDir dist-b",
    "build:backend":  "pnpm --filter backend build",
    "build:a":        "pnpm build:shared && pnpm build:front:a && pnpm build:backend",
    "build:b":        "pnpm build:shared && pnpm build:front:b && pnpm build:backend",
    "build:all":      "pnpm build:a && pnpm build:b"
  }
}

创建过程二:搭建公共包

创建公共包

ruby 复制代码
// packages/shared/package.json
{
  "name": "@repo/shared",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch"
  },
  "dependencies": { "zod": "^4.0.0" },
  "devDependencies": { "tsup": "^8.3.0", "typescript": "^5.6.0" }
}

创建根 tsconfig.base.json

json 复制代码
{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

创建共享包 core

bash 复制代码
mkdir -p packages/core/src/schema
cd packages/core
pnpm init
pnpm add zod
pnpm add -D tsup typescript
cd ../..

修改packages/core/package.json

json 复制代码
{
  "name": "@repo/shared",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch"
  },
  "dependencies": { "zod": "^4.0.0" },
  "devDependencies": { "tsup": "^8.3.0", "typescript": "^5.6.0" }
}

创建 packages/core/tsconfig.json

json 复制代码
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": { "rootDir": "src", "outDir": "dist" },
  "include": ["src"]
}

创建 packages/core/tsup.config.ts

ts 复制代码
import { defineConfig } from 'tsup'

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  dts: true,
  clean: true,
  sourcemap: true,
  target: 'es2022',
})

packages/core/src/schema/user.ts

ts 复制代码
import { z } from 'zod'

export const userSchema = z.object({
  id: z.string(),
  name: z.string().min(1),
  email: z.string().email(),
})

export type User = z.infer<typeof userSchema>

packages/shared/src/index.ts

ts 复制代码
export * from './schema/user'

验证契约层可构建:

shell 复制代码
pnpm --filter core build
# 看到 packages/core/dist/index.js + index.d.ts + index.cjs 即成功

创建过程三:搭建前端包

构建项目

bash 复制代码
pnpm create vite apps/frontend --template vue-ts   # 脚手架,不自动 install

引入共享包

ts 复制代码
import { createApp } from 'vue'
import { userSchema } from '@repo/shared'   // ← 引入共享包
import App from './App.vue'

// 演示:前后端共用的同一份 zod schema
console.log(userSchema.safeParse({ id: '1', name: 'tom', email: 'a@b.com' }))

createApp(App).mount('#app')

创建过程三:搭建后端包

搭建项目

bash 复制代码
cd apps
npx @nestjs/cli new backend --package-manager pnpm --skip-install --skip-git
cd ..

引入包

javascript 复制代码
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { userSchema } from '@repo/shared'   // ← 引入共享包

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  // 后端入参校验直接用前端同一份 schema
  console.log(userSchema.safeParse({ id: 'x', name: '', email: 'bad' }).success)
  await app.listen(process.env.PORT ?? 3000)
}
bootstrap()

爬坑记录

ts版本过高,导致打包公共文件报错,将版本至5.8即可

shell 复制代码
pnpm add -D typescript@~5.8.0
报错 复制代码
PS C:\code_test\project> pnpm --filter core build
Scope: all 2 workspace projects
✓ Lockfile passes supply-chain policies (verified 9m ago)
Lockfile is up to date, resolution step is skipped
Already up to date
Done in 1.1s using pnpm v11.26.0
$ tsup
CLI Building entry: src/index.ts
CLI Using tsconfig: tsconfig.json
CLI tsup v8.5.1
CLI Using tsup config: C:\code_test\project\packages\shared\tsup.config.ts
CLI Target: es2022
CLI Cleaning output folder
ESM Build start
CJS Build start
ESM dist\index.js     210.00 B
ESM dist\index.js.map 468.00 B
ESM ⚡️ Build success in 133ms
CJS dist\index.cjs     1.26 KB
CJS dist\index.cjs.map 568.00 B
CJS ⚡️ Build success in 135ms

node:internal/event_target:1118
  process.nextTick(() => { throw err; });
                           ^
TypeError: Cannot read properties of undefined (reading 'useCaseSensitiveFileNames')
    at node_modules/.pnpm/rollup-plugin-dts@6.1.1_rollup@4.53.2_typescript@5.7.3/node_modules/rollup-plugin-dts/dist/rollup-plugin-dts.cjs (C:\code_test\project\node_modules\.pnpm\tsup@8.5.1_typescript@7.0.2\node_modules\tsup\dist\rollup.js:4857:37)
    at __require2 (C:\code_test\project\node_modules\.pnpm\tsup@8.5.1_typescript@7.0.2\node_modules\tsup\dist\chunk-TWFEYLU4.js:14:50)
    at Object.<anonymous> (C:\code_test\project\node_modules\.pnpm\tsup@8.5.1_typescript@7.0.2\node_modules\tsup\dist\rollup.js:6777:17)
    at Module._compile (node:internal/modules/cjs/loader:1830:14)
    at Object..js (node:internal/modules/cjs/loader:1961:10)
    at Module.load (node:internal/modules/cjs/loader:1553:32)
    at Module._load (node:internal/modules/cjs/loader:1355:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
    at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
    at MessagePort.<anonymous> (node:internal/main/worker_thread:223:26)

Node.js v24.15.0
C:\code_test\project\packages\shared:
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] shared@1.0.0 build: `tsup`
Exit status 1

报错二

vbnet 复制代码
PS C:\code_test\project\apps> nest new hmprd_backend
✨  We will scaffold your app in a few seconds..

✔ Which package manager would you ❤️  to use? pnpm
CREATE hmprd_backend/.prettierrc (56 bytes)
CREATE hmprd_backend/eslint.config.mjs (934 bytes)
CREATE hmprd_backend/nest-cli.json (179 bytes)
CREATE hmprd_backend/package.json (2054 bytes)
CREATE hmprd_backend/README.md (5134 bytes)
CREATE hmprd_backend/tsconfig.build.json (101 bytes)
CREATE hmprd_backend/tsconfig.json (702 bytes)
CREATE hmprd_backend/src/app.controller.ts (286 bytes)
CREATE hmprd_backend/src/app.module.ts (259 bytes)
CREATE hmprd_backend/src/app.service.ts (150 bytes)
CREATE hmprd_backend/src/main.ts (236 bytes)
CREATE hmprd_backend/src/app.controller.spec.ts (639 bytes)
CREATE hmprd_backend/test/jest-e2e.json (192 bytes)
CREATE hmprd_backend/test/app.e2e-spec.ts (754 bytes)

▹▹▹▹▹ Installation in progress... ☕
Failed to execute command: pnpm install --strict-peer-dependencies=false --reporter=silent
✖ Installation in progress... ☕
🙀  Packages installation failed!
In case you don't see any errors above, consider manually running the failed command pnpm install --strict-peer-dependencies=false to see more details on why it errored out.
                                         
                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.
                                         
                                         
               🍷  Donate: https://opencollective.com/nest
                                         
PS C:\code_test\project\apps> 
PS C:\code_test\project\apps> 
PS C:\code_test\project\apps> pnpm install --strict-peer-dependencies=false
Scope: all 4 workspace projects
✓ Lockfile passes supply-chain policies (verified 1m ago)
Lockfile is up to date, resolution step is skipped
Already up to date
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @parcel/watcher@2.6.0, unrs-resolver@1.12.2

Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
PS C:\code_test\project\apps> 
PS C:\code_test\project\apps> 
PS C:\code_test\project\apps> nest new hmprd_backend                       
PS C:\code_test\project\apps> ^C
PS C:\code_test\project\apps> ^C
PS C:\code_test\project\apps> cd .\hmprd_backend\
PS C:\code_test\project\apps\hmprd_backend> 
PS C:\code_test\project\apps\hmprd_backend> 
PS C:\code_test\project\apps\hmprd_backend> pnpm i
Scope: all 4 workspace projects
✓ Lockfile passes supply-chain policies (verified 1m ago)
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @parcel/watcher@2.6.0, unrs-resolver@1.12.2

Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
PS C:\code_test\project\apps\hmprd_backend> pnpm approve-builds
✔ Choose which packages to build (Press <space> to select, <a> to toggle all, <i> to invert selection) @parcel/watcher, unrs-resolver
✔ The next packages will now be built: @parcel/watcher, unrs-resolver.
Do you approve? Yes
../../node_modules/.pnpm/unrs-resolver@1.12.2/node_modules/unrs-resolver: Running postinstall script, done in 2.5s
../../node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher: Running install script, done in 1.6s
PS C:\code_test\project\apps\hmprd_backend> 
PS C:\code_test\project\apps\hmprd_backend> 
相关推荐
Profile排查笔记1 小时前
指纹浏览器推荐:用一套验收清单筛选 Profile、代理与自动化能力
前端·人工智能·后端·自动化
jianpeng的工程笔记2 小时前
Colab Kaggle 防断开脚本
javascript·深度学习·kaggle·colab
yume_sibai2 小时前
CSS2 核心知识点完全总结(选择器 + 三大特性 + 常用属性)
前端·css
掘金者阿豪2 小时前
不装 SSH 客户端也能连服务器:用 WebSSH 把终端搬进浏览器
前端
Bug修理工Bubble2 小时前
Optional:为什么一个 String? 能让代码少掉很多崩溃?
前端
巴勒个啦2 小时前
2026 年 CSS 选型真相:我用 Tailwind v4 + 原生新特性重构了一个组件库
前端·angular.js
LEE2 小时前
别再堆 AGENTS.md 了:前端团队如何把 AI Coding 做成一套可执行的工程系统
前端·后端
三十而立洋2 小时前
JavaScript 原型链:一张图讲透「对象继承」的底层真相
前端·javascript
工具派2 小时前
markdown在线编辑器怎么选?渲染管线的3个坑和md转PDF跑版记录
前端·后端