无法使用 ts-jest 和 monorepo 找到模块“@foo”

问题描述 投票:0回答:1

我有一个使用 TypeScript、WebPack 和 ts-jest 的 monorepo。它构建正确,但单元测试在其中一个子项目上失败

./demo
,原因是:

Cannot find module '@mlhaufe/graphics/adapters' or its corresponding type declarations.

/tsconfig.json

{
    "compilerOptions": {
        "baseUrl": "./packages",
        "paths": {
            "@mlhaufe/graphics/*": [
                "lib/src/*"
            ]
        }
    },
    "references": [
        {
            "path": "./packages/lib"
        },
        {
            "path": "./packages/demo"
        }
    ],
    "files": [],
    "exclude": [
        "node_modules"
    ]
}

/packages/demo/tsconfig.json

{
   ...
    "references": [
        {
            "path": "../lib"
        }
    ]

/jest.config.mjs

import fs from 'fs';
import path from 'path';
import url from 'url';
import { pathsToModuleNameMapper } from 'ts-jest';
import tsconfig from './tsconfig.json' assert { type: 'json' };

const { compilerOptions } = tsconfig,
    __filename = url.fileURLToPath(import.meta.url),
    __dirname = path.dirname(__filename),
    packageNames = fs.readdirSync(path.resolve(__dirname, './packages'));

/** @type {import('jest').Config} */
export default {
    rootDir: compilerOptions.baseUrl,
    verbose: true,
    testPathIgnorePatterns: [
        '<rootDir>/node_modules/',
    ],
    reporters: [
        'default',
        ['jest-junit', { outputDirectory: './coverage' }]
    ],
    // <https://jestjs.io/docs/next/configuration#projects-arraystring--projectconfig>
    projects: packageNames.map((name) => ({
        displayName: name,
        transform: {
            '^.+\\.mts$': ['ts-jest', { useESM: true }]
        },
        moduleFileExtensions: ['js', 'mjs', 'mts'],
        roots: [`<rootDir>`],
        modulePaths: [compilerOptions.baseUrl],
        // required due to ts-jest limitation
        // <https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/#support-mts-extension>
        resolver: '<rootDir>/mjs-resolver.ts',
        // Used the path aliases in tsconfig.json
        moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
            prefix: '<rootDir>',
            useESM: true
        }),
        // moduleNameMapper: {
        //     '@mlhaufe/graphics/(.+)': '<rootDir>/packages/lib/src',
        //     '^(\\.\\.?/.*)\\.mjs$': ['$1.mts', '$0']
        // },
        testMatch: [`<rootDir>/packages/${name}/**/*.test.mts`],
        testPathIgnorePatterns: [`<rootDir>/packages/${name}/dist/`]
    }))
};

/package.json

{
    ...
    "workspaces": [
        "packages/*"
    ],
    "engines": {
        "node": ">=16.0.0"
    },
    "scripts": {
        "build": "npm run build --workspaces",
        "build:lib": "npm run build --workspace=packages/lib",
        "build:demo": "npm run build --workspace=packages/demo",
        "test": "jest --coverage",
        "test:lib": "jest --selectProjects=lib --coverage",
        "test:demo": "jest --selectProjects=demo --coverage",
        "serve:demo": "npm run serve --workspace=packages/demo"
    },
    ...
}

我不明白为什么 ts-jest 找不到模块,而 webpack+typescript 没有问题。我还没有弄清楚

projects
属性之外的设置与内部设置之间的关系。我的假设是这些将是全球性的并适用于每个项目,但我怀疑这不是真的。

如有任何反馈,我们将不胜感激。我还没有看到一篇关于如何实现这一点的一致(现代)文章。

typescript jestjs es6-modules ts-jest monorepo
1个回答
0
投票

据我了解这个问题:jest 正在解析 monorepo 中的本地模块,而无需事先转译它们。我将这个答案中的解决方案视为一种解决方法,而不是最终的事情。

我提出的解决方案基本上是将导入路径转换为不被视为外部包,而是作为路径导入。

我的

jest.config.ts
文件:

const config: Config = {
  preset: 'ts-jest/presets/js-with-ts-esm',
  resolver: '<rootDir>/jest.resolve.cjs',
  // the rest of the config is omitted, the above two lines are the important part
}

export default config

jest.resolve.cjs
文件:

const fs = require('node:fs')
const path = require('node:path')
const resolver = require('ts-jest-resolver')

const localModules = new Map(
  fs.readdirSync(path.join(__dirname, 'packages'), { withFileTypes: true })
    .filter((dirent) => dirent.isDirectory())
    .map((dirent) => {
      const directory = path.join(__dirname, 'packages', dirent.name)
      const pkg = require(path.join(directory, 'package.json'))
      const main = path.join(directory, pkg.main)

      return [pkg.name, main]
    }),
)

/**
 * @param {string} path
 * @param {import('jest-resolve').ResolverOptions} options
 * @returns {string}
 */
module.exports = function resolve (path, options) {
  return resolver(localModules.get(path) ?? path, options)
}

您可能已经知道了,我的例子中的所有本地模块都存储在

packages
目录中 - 因此,如果您的情况不同,您可能需要相应地调整它。

亲爱的读者:如果您遇到了这个问题,我可以放心地假设您已经足够先进来理解代码,所以我将省略对其的解释:)

© www.soinside.com 2019 - 2024. All rights reserved.