typescript - مسیرهای مطلق (baseUrl) خطا میدهد: نمیتوان ماژول را پیدا کرد


من در حال تنظیم یک پیکربندی برای اجرای تستهایم در یک برنامه create-react-app + typescript هستم (از آن که برنامه را خارج کردهام). من از jest + enzyme استفاده میکنم. در فایل tsconfig.json خود baseUrl='./src' را تنظیم کردهام تا بتوانم هنگام وارد کردن ماژولها از مسیرهای مطلق استفاده کنم. برای مثال، این یک دستور import معمولی در یکی از فایلهای من است:

import LayoutFlexBoxItem from 'framework/components/ui/LayoutFlexBoxItem';

میتوانید ببینید که مسیر مطلق است (از پوشه /src) و نسبی نیست. این وقتی که در حالت دیباگ اجرا میکنم ( yarn start ) به خوبی کار میکند.

اما وقتی تست خود را اجرا میکنم ( yarn test )، این خطا را دریافت میکنم:

 Cannot find module 'framework/components/Navigation' from 'index.tsx'

بنابراین به نظر میرسد که jest قادر به حل این مسیر مطلق نیست، اگرچه من آن را در فایل tsconfig.json خود تنظیم کردهام. این فایل tsconfig.json من است:

{
  "compilerOptions": {
    "outDir": "dist",
    "module": "esnext",
    "target": "es5",
    "lib": ["es6", "dom"],
    "sourceMap": true,
    "allowJs": true,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDir": "src",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "noUnusedLocals": true,
    "baseUrl": "./src"    
  },
  "exclude": [
    "node_modules",
    "build",
    "dist",
    "config",    
    "scripts",
    "acceptance-tests",
    "webpack",
    "jest",
    "src/setupTests.ts"
  ]
}

حالا میتوانم ببینم که یک tsconfig.test.json تولید شده در ریشه پروژه من وجود دارد. این تنظیمات ts است که برای تست استفاده میشود. و این هم محتوای آن است:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs"
  }
}

همانطور که می بینید، «module» اینجا commonjs است در حالی که در پیکربندی پیش فرض esnext است. آیا این می تواند یکی از دلایل باشد؟

آیا کسی توانسته است پروژه تایپاسکریپت خود را با Jest و مسیر مطلق تست واحد کند؟ یا این یک باگ شناخته شده است؟ از آنجا که من از پیکربندی پیشفرض خارج شدهام، آیا تنظیماتی وجود دارد که باید در پیکربندی webpack خود قرار دهم؟

از نظر و پیشنهاد شما متشکرم.

پاسخ شماره ۱

من با همان مشکل دست و پنجه نرم میکردم و در واقع مشخص شد که یک تغییر ساده به نظر میرسد کارساز باشد.

من تازه فیلد moduleDirectories را در jest.config.js بهروزرسانی کردم.

قبل

moduleDirectories: ['node_modules']

بعد

moduleDirectories: ['node_modules', 'src']

پاسخ شماره ۲

همانطور که بسیاری در اینجا اشاره کردند، moduleNameMapper در jest.config.js نیاز دارد مسیرهای مشخصشده در tsconfig.json را تعریف کند. برای مثال، اگر مسیرهایی در tsconfig.json به صورت زیر تعریف شده باشند

// tsconfig.json
{
 ...
 "baseUrl": "src",
 "paths": {
    "@alias/*": [ 'path/to/alias/*' ]
 }
 ...
}

سپس فایل jest.config.js شما نیاز دارد که آن مسیرها را در moduleNameMapper به فرمت زیر ارائه دهد:

// jest.config.js
module.exports = {
    'roots': [
        '<rootDir>/src'
    ],
    'transform': {
        '^.+\\.tsx?$': 'ts-jest'
    },
    'moduleNameMapper': {
         '@alias/(.*)': '<rootDir>/src/path/to/alias/$1'
    }
};

:

// jest.config.js

function makeModuleNameMapper(srcPath, tsconfigPath) {
    // Get paths from tsconfig
    const {paths} = require(tsconfigPath).compilerOptions;

    const aliases = {};

    // Iterate over paths and convert them into moduleNameMapper format
    Object.keys(paths).forEach((item) => {
        const key = item.replace('/*', '/(.*)');
        const path = paths[item][0].replace('/*', '/$1');
        aliases[key] = srcPath + '/' + path;
    });
    return aliases;
}

const TS_CONFIG_PATH = './tsconfig.json';
const SRC_PATH = '<rootDir>/src';

module.exports = {
    'roots': [
        SRC_PATH
    ],
    'transform': {
        '^.+\\.tsx?$': 'ts-jest'
    },
    'moduleNameMapper': makeModuleNameMapper(SRC_PATH, TS_CONFIG_PATH)
};

پاسخ شماره ۳

اینجا نحوه کار کردن moduleNameMapper را نشان میدهم.

با تنظیمات زیر در tsconfig من:

    "paths": {
      "@App/*": [
        "src/*"
      ],
      "@Shared/*": [
        "src/Shared/*"
      ]
    },

در اینجا moduleNameMapper آورده شده است:

"moduleNameMapper": {
  "@App/(.*)": "<rootDir>/src/$1",
  "@Shared/(.*)": "<rootDir>/src/Shared/$1"
}

پاسخ شماره ۴

این بخش زیر را در فایل package.json خود اضافه کنید. بعد از اعمال تغییرات، فراموش نکنید که ناظران تست خود را مجدداً راهاندازی کنید.

  "jest": {
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "roots": [
      "src"
    ],
    "testRegex": ".spec.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "coverageDirectory": "../coverage",
    "testEnvironment": "node",
    "moduleNameMapper": {
      "src/(.*)": "<rootDir>/src/$1"
    }
  }

پاسخ شماره ۵

برای من، فقط لازم بود اضافه کنم

"modulePaths": ["<rootDir>/src"],

به فایل jest.config.js من.

پاسخ زیر برای تغییر moduleDirectories منجر به این خطا شد:

Jest encountered an unexpected token

Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

By default "node_modules" folder is ignored by transformers.

Here's what you can do:
 • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
 • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
 • If you need a custom transformation specify a "transform" option in your config.
 • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation

استفاده از :

modulePaths: ["node_modules", "<rootDir>/src"],

از خواندن مستندات به نظر میرسد که این یک لیست از دایرکتوریهای اضافی است و بنابراین node_modules غیرضروری است.

پاسخ شماره ۶

برای کسانی که از مسیر مطلق استفاده میکنند اما از نگاشتهای نامگذاری شده استفاده نمیکنند، این روش برای من کار کرد:

jsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
  }
}

jest.config.js
const config = {
  moduleDirectories: ['node_modules', '<rootDir>'],
};

پاسخ #۷

راهحل با استفاده از بهترین روش

این خطا به دلیل استفاده از مسیرهای مطلق در دستورات import پروژههای TypeScript/Nest.js/Angular ما هنگام استفاده از Jest رخ میدهد. رفع آن با گزینههای moduleDirectories و moduleNameMapper ممکن است مشکل شما را بهصورت موقت حل کند اما مشکلاتی با بستههای دیگر مانند این مشکل TypeORM ایجاد میکند. همچنین، سازنده فریمورک Nest.js اینجا و اینجا پیشنهاد میکند که استفاده از مسیرهای مطلق یک روش نادرست است. مستندات رسمی TypeScript توصیه میکند که برای ماژولهای خود از مسیرهای نسبی استفاده کنیم.


مسیر مطلق در مقابل مسیر نسبی

عبارت import با مسیر مطلق شبیه به این است:

import { AuthService } from 'src/auth/auth.service'

عبارت import با مسیر نسبی شبیه به این است:

import { AuthService } from '../auth/auth.service'

تنظیمات VS Code

ویاس کد به صورت پیشفرض از مسیر مطلق استفاده میکند همانطور که در بالا نشان داده شده است، زمانی که ما با تکمیل خودکار کد یا Command/Ctrl + . وارد میکنیم. ما نیاز داریم این تنظیم پیشفرض را تغییر دهیم تا از مسیرهای نسبی استفاده شود.

به تنظیمات VS Code بروید و به دنبال یک تنظیم بگردید: Import Module Specifier. آن را از shortest به relative تغییر دهید.

حال از این به بعد، VS Code به صورت خودکار از مسیرهای نسبی وارد میکند.


رفع مشکلات واردات در پروژه

حال در فایلهای پروژه، به دنبال مسیرهای مطلق در ایمپورتها بگردید که شبیه مثال بالا هستند و آنها را حذف کنید. برای بستههایی که حذف کردهاید، خطا مشاهده خواهید کرد. کافی است از پیشنهادات خودکار ایمپورت استفاده کرده و دوباره آنها را ایمپورت کنید. این بار با استفاده از مسیرهای نسبی ایمپورت خواهند شد. این مرحله بسته به اندازه پروژه شما ممکن است خستهکننده باشد اما در بلندمدت ارزشش را دارد.


امیدوارم خوب پیش بره! به سلامتی!