NestJS构建异常:dist目录嵌套src文件夹,如何恢复正常输出?
问题描述
原本使用"build:dev": "yarn nest build && cp package.json dist/package.json && cp -R prisma/schema dist/schema && cp .env.development dist/.env"命令构建Nest.js项目时,编译后的文件会直接输出到./dist目录,与src内部结构一致。但添加vitest及相关配置后,构建产物开始将整个src文件夹放入./dist(即dist/src/xxx),导致AWS EC2生产环境无法正常运行;同时部署后需要将.env重命名为.env.development才能执行Prisma迁移,引发数据库异常。
目录结构对比
- 异常结构:
dist目录下包含完整的src文件夹,编译文件位于dist/src/层级 - 期望结构:编译后的文件直接存放在
dist根目录,与原src内部层级完全对应
相关配置文件
package.json
{ "name": "mypro-backend", "version": "1.0.0", "description": "mypro", "type": "commonjs", "scripts": { "start:dev": "NODE_ENV=development nest start --watch", "start:prod": "NODE_ENV=production nest start --watch", "start:debug": "nest start --debug --watch", "build:dev": "yarn nest build && cp package.json dist/package.json && cp -R prisma/schema dist/schema && cp .env.development dist/.env", "build:prod": "yarn nest build && cp package.json dist/package.json && cp -R prisma/schema dist/schema && cp .env.production dist/.env", "studio:dev": "dotenv -e .env.development -- yarn prisma studio --port 5558", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "test": "vitest run", "test:watch": "vitest", "test:cov": "vitest run --coverage", "test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false", "test:e2e": "dotenv -e .env.development -- vitest run --config ./vitest.config.e2e.ts" }, "dependencies": { "@aws-sdk/client-s3": "^3.729.0", "@aws-sdk/s3-request-presigner": "^3.741.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/throttler": "^6.3.0", "@prisma/client": "^6.2.1", "axios": "^1.7.9", "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.7", "dotenv": "^16.4.7", "moment": "^2.30.1", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "path": "^0.12.7", "pdfkit": "^0.16.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "uuid": "^11.0.5", "zod": "^3.24.1", "zod-validation-error": "^3.4.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@rocketseat/eslint-config": "^2.2.2", "@swc/core": "^1.10.12", "@types/bcryptjs": "^2.4.6", "@types/express": "^5.0.0", "@types/multer": "^1.4.12", "@types/node": "^20.3.1", "@types/passport-jwt": "^4.0.1", "@types/pdfkit": "^0.13.9", "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "@vitest/coverage-v8": "^3.0.5", "eslint": "^8.57.0", "prisma": "^6.2.1", "source-map-support": "^0.5.21", "supertest": "^7.0.0", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3", "unplugin-swc": "^1.5.1", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.5" }, "packageManager": "yarn@1.22.22" }
tsconfig.json
{ "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "strict": true, "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": true, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, "paths": { "@/*": [ "src/*" ] }, "types": [ "vitest/globals", "Multer" ] } }
tsconfig.build.json
{ "extends": "./tsconfig.json", "exclude": [ "node_modules", "test", "dist", "**/*spec.ts" ] }
nest-cli.json
{ "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } }
vitest.config.ts
import swc from 'unplugin-swc' import { defineConfig } from 'vitest/config' import tsConfigPaths from 'vite-tsconfig-paths' export default defineConfig({ test: { globals: true, root: './', }, plugins: [ tsConfigPaths(), swc.vite({ module: { type: 'es6' }, }), ], })
vitest.config.e2e.ts
import swc from 'unplugin-swc' import { defineConfig } from 'vitest/config' import tsConfigPaths from 'vite-tsconfig-paths' export default defineConfig({ test: { include: ['**/*.e2e-spec.ts'], globals: true, root: './', setupFiles: ['./test/setup-e2e.ts'], }, plugins: [ tsConfigPaths(), swc.vite({ module: { type: 'es6' }, }), ], })
问题原因
添加vitest后,tsconfig.json中新增的"types": ["vitest/globals"]以及vite-tsconfig-paths插件干扰了Nest CLI的编译路径解析逻辑。加上tsconfig.json的baseUrl设为./,导致Nest CLI编译时错误地将整个src目录作为输入单元,而非将src下的内容作为编译源输出到dist根目录。
修复方案
方案1:明确指定编译源(最简修复)
修改tsconfig.build.json,添加include字段,强制仅编译src下的所有文件:
{ "extends": "./tsconfig.json", "include": ["src/**/*"], "exclude": [ "node_modules", "test", "dist", "**/*spec.ts" ] }
方案2:强制Nest CLI的根目录配置
在nest-cli.json的compilerOptions中添加"rootDir": "./src",明确告诉Nest CLI以src为编译根目录,输出内容直接到dist:
{ "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "rootDir": "./src" } }
方案3:隔离测试与生产的TS配置(更规范)
创建单独的测试环境TS配置,避免vitest影响生产编译:
- 创建
tsconfig.test.json:
{ "extends": "./tsconfig.json", "compilerOptions": { "types": ["vitest/globals", "Multer"] }, "include": ["src/**/*", "test/**/*"] }
- 修改
tsconfig.json,移除types中的vitest/globals:
{ "compilerOptions": { // ...保留其他原有配置 "types": ["Multer"] } }
- 更新两个vitest配置文件,指定使用测试专用TS配置:
// vitest.config.ts 和 vitest.config.e2e.ts 都需要修改 import swc from 'unplugin-swc' import { defineConfig } from 'vitest/config' import tsConfigPaths from 'vite-tsconfig-paths' import path from 'path' export default defineConfig({ test: { // ...保留原有测试配置 }, plugins: [ tsConfigPaths({ projects: ['./tsconfig.test.json'] }), swc.vite({ module: { type: 'es6' }, }), ], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, })
验证修复
执行yarn build:dev后,检查dist目录结构,确认编译文件直接输出到dist根目录;同时测试Prisma迁移是否可通过.env正常执行。
内容的提问来源于stack exchange,提问作者Nilton Schumacher F

