pnpm Monorepo中Jest无法解析共享UI组件的配置问题
问题描述
在pnpm管理的React+TypeScript+Vite Monorepo项目中,Hello.spec.tsx测试文件无法解析位于/libs/ui库中的Test组件。使用标准导入语法import { Test } from "@libs/ui"时,Jest抛出模块无法找到的错误,但普通测试文件(如add.spec.ts)可正常运行,不确定问题源于Monorepo跨包模块解析还是Jest路径配置。
相关代码示例
pds/src/components/Hello.tsx
import { Test } from "@libs/ui"; // Test组件来自monorepo/libs/ui export default () => ( <> <p>Hello Diego was here</p> <Test /> </> );
pds/src/components/Hello.spec.tsx
/** * @jest-environment jsdom */ import { render, screen } from '@testing-library/react' import Component from './Hello'; import '@testing-library/jest-dom' test('renders paragraph and Test component', () => { render(<Component />) // 检查段落是否渲染 expect(screen.getByText(/Hello Diego was here/i)).toBeInTheDocument() })
/libs/ui/src/Test.tsx
export const Test: React.FC = () => <p>This is a Test Component</p>
可正常运行的测试文件示例
/pds/src/add.ts
export const add = (a: number, b: number) => { return a + b; }
/pds/src/add.spec.ts
import { add } from "./add"; describe("add function", () => { it("should add two numbers together", () => { const result = add(10, 5); expect(result).toBe(15); }); });
问题复现步骤
- 安装
pnpm - 创建
monorepo文件夹 - 进入
monorepo执行pnpm init - 创建
monorepo/pnpm-workspace.yaml并填入对应内容 - 创建
apps和libs两个文件夹 - 进入
apps执行pnpm create vite@latest,选择typescript和react模板 - 进入
libs执行pnpm create vite@latest,选择typescript和react模板 - 替换
pds和ui项目的package.json、vite.config.ts、tsconfig.json内容
项目结构
/monorepo ├── pnpm-workspace.yaml ├── package.json ├── jest.config.ts ├── tsconfig.build.json ├── tsconfig.json ├── /apps │ └── /pds │ ├── package.json │ ├── vite.config.ts │ └── tsconfig.json └── /libs └── /ui ├── tsconfig.json ├── vite.config.ts └── package.json
配置文件详情
/monorepo/pnpm-workspace.yaml
packages: - 'apps/*' - 'libs/*'
/monorepo/package.json
{ "name": "monorepo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "pds": "pnpm --filter @apps/pds", "ui": "pnpm --filter @libs/ui", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@eslint/js": "^9.19.0", "@testing-library/jest-dom": "^6.6.3", "@types/jest": "^29.5.14", "eslint": "^9.20.0", "globals": "^15.14.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "~5.7.2", "typescript-eslint": "^8.22.0", "vite": "^6.1.0" } }
/monorepo/jest.config.ts
import { Config } from 'jest'; const config: Config = { // verbose: true, preset: "ts-jest", testEnvironment: "jsdom", transform: { "^.+\\.(ts|tsx)$": "ts-jest", }, projects: [ { displayName: 'pds', preset: 'ts-jest', testEnvironment: 'jsdom', testMatch: ['<rootDir>/apps/pds/src/**/*.spec.tsx', '<rootDir>/apps/pds/src/**/*.spec.ts'], transform: { '^.+\\.(ts|tsx)$': 'ts-jest' }, } ], }; module.exports = config;
解决方案
1. 配置Jest模块映射
Jest无法自动识别pnpm workspace的别名,需手动在jest.config.ts中添加moduleNameMapper,将@libs/ui映射到实际源码路径:
import { Config } from 'jest'; import path from 'path'; const config: Config = { preset: "ts-jest", testEnvironment: "jsdom", transform: { "^.+\\.(ts|tsx)$": "ts-jest", }, projects: [ { displayName: 'pds', preset: 'ts-jest', testEnvironment: 'jsdom', testMatch: ['<rootDir>/apps/pds/src/**/*.spec.tsx', '<rootDir>/apps/pds/src/**/*.spec.ts'], transform: { '^.+\\.(ts|tsx)$': 'ts-jest' }, // 添加模块映射 moduleNameMapper: { '^@libs/ui$': path.resolve(__dirname, 'libs/ui/src'), '^@libs/ui/(.*)$': path.resolve(__dirname, 'libs/ui/src/$1'), }, // 确保Jest处理tsx文件扩展名 moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], } ], }; module.exports = config;
2. 同步TypeScript路径配置
确保根目录tsconfig.json中配置了对应路径别名,且pds的tsconfig.json继承该配置:
/monorepo/tsconfig.json
{ "compilerOptions": { "baseUrl": ".", "paths": { "@libs/ui": ["libs/ui/src"], "@libs/ui/*": ["libs/ui/src/*"] } }, "include": ["apps/**/*", "libs/**/*"] }
/apps/pds/tsconfig.json(需添加extends)
{ "extends": "../../tsconfig.json", // 其他原有配置... }
3. 确保包已正确链接
执行pnpm install让pnpm自动处理workspace包的依赖链接,或手动执行:
pnpm --filter @apps/pds add @libs/ui@workspace:*
4. 检查UI包的入口配置
确保/libs/ui/package.json中指定了正确的入口文件,若有统一导出的index.ts:
{ "name": "@libs/ui", "main": "./src/index.ts", "exports": { ".": "./src/index.ts", "./src/*": "./src/*" } }
(若未创建index.ts,需在其中导出Test组件:export { Test } from './Test';)
原因说明
Jest不会自动读取Vite的解析配置或pnpm workspace的别名规则,必须通过moduleNameMapper手动映射别名到实际文件路径。同时需要同步TypeScript的paths配置,保证编译和测试阶段的路径解析一致。
内容的提问来源于stack exchange,提问作者Diego Salas

