约定小结
- monorepo是一种代码组织结构,和最终的部署方式无关
- monorepo的意义在于抽离公共的内容
- 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>