#typescript #jestjs #babeljs
#typescript #jestjs #babeljs
Вопрос:
Я пишу Node.js код (на машинописном языке) с использованием ESModules, к которому мне нужен доступ __dirname
. Чтобы получить доступ к ESM-эквиваленту __dirname
in CommonJS, я вызываю dirname(fileURLToPath(import.meta.url))
. Я также пишу тесты в Jest с помощью TypeScript. Используя это руководство, я настроил Babel. Когда я запускаю jest
команду, я получаю
const DIRNAME = (0, _path.dirname)((0, _url.fileURLToPath)(import.meta.url));
^^^^
SyntaxError: Cannot use 'import.meta' outside a module
Вот файлы, которые я написал
someCode.ts
:
import { dirname } from "path";
import { fileURLToPath } from "url";
const DIRNAME = dirname(fileURLToPath(import.meta.url));
export const x = 5;
someCode.test.ts
:
import { x } from "./someCode";
it("Returns 5 as the result", () => {
expect(x).toEqual(5);
});
.babelrc.json
:
{
"presets": [
["@babel/preset-env", { "targets": { "node": "current" } }],
"@babel/preset-typescript"
]
}
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node"
},
"include": ["./src/**/*.ts", "./src/**/*.js"]
}
package.json
:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.7",
"@babel/preset-env": "^7.12.7",
"@babel/preset-typescript": "^7.12.7",
"jest": "^26.6.3",
"typescript": "^4.1.2"
}
}
Окружающая среда:
- Узел: 14.1.0
- Смотрите
package.json
версии модулей
Комментарии:
1. Вы следуете общему руководству, в котором используются модули CommonJS. Для встроенного ESM см. jestjs.io/docs/en/ecmascript-modules
2. Я ищу ответ на тот же вопрос. Перемещение Jest в модули ESM кажется чрезмерным.
Ответ №1:
Решение: модульизировать код ESM и смоделировать его.
Я также недавно столкнулся с этой проблемой, и мне пришлось:
- Экспортируйте конкретную проблему ESM, например
import.meta
, функциональность, в собственный файл / функцию «utils». - Затем создайте макет функции, которая не требует специфичной для ESM функциональности, в каталоге mocks.
- в файлах, которые используют эту функциональность, объявите
jest.mock("/path/to/that/file.ts")
Процесс будет выглядеть примерно так:
Исходная структура файла
src/
--/someCode.ts
--/__tests__/
/--/--/someCode.test.ts
// someCode.ts
...
const getApiUrl = () => {
...
const url = import.meta.env.SOME_ENV_VAR_HERE;
...
return url;
};
...
Новая структура файла
Заголовок
src/
--/someCode.ts
--/utils.js
--/__mocks__/
--/--/utils.ts
--/__tests__/
--/--/someCome.test.ts
а затем в самих файлах:
// someCode.ts
...
import { getApiUrl } from "./utils.ts";
...
// utils.js
export const getApiUrl = () => {...};
и для тестов:
// __mocks__/utils.ts
export const getApiUrl = jest.fn(() => 'some.url');
// __tests__/utils.test.ts
...
jest.mock("src/util.ts");
...
describe(...);
Комментарии:
1. Это то, что привело к моему решению. Единственная разница в том, что я делаю что-то вроде этого: «jest.mock(‘$ lib / variables’, () => ({ __ esModule: true, переменные: { apiUrl: ‘ localhost:2222 ‘ } })); «
2. @TimoHermans не могли бы вы опубликовать решение, которое сработало для вас в ответ
3. это решение немного шаткое. Он работает в спецификации самого root, но по какой-то причине может привести к неправильному импорту макетов файлов в другом месте. Сейчас я изучаю решение babel
4. Это решило проблему для меня. Большое спасибо.
Ответ №2:
Мы используем import.meta.url
для использования web workers, например, используя встроенную поддержку worker в Webpack 5. Это отлично работает, но не работает при запуске тестов Jest.
babel-vite-preset
насколько я могу судить, не справляется с этим. Главный ответ об извлечении и издевательстве над используемым кодом import.meta
действительно работает, но громоздок.
Я обнаружил, что в настоящее время лучшим решением является использование babel-plugin-transform-import-meta . Этот простой плагин заменяет import.meta.url
синтаксис на Node.js код для получения URL-адреса текущего файла, чтобы он хорошо работал в тестах. Обратите внимание, что вы захотите, чтобы этот плагин был активен только при запуске тестов.
Комментарии:
1. Спасибо, именно то, что мне нужно!
2. Как заставить его работать только в jest? Я пытался заставить что-то работать с create-react-app 5 / webpack 5 здесь, но не смог запустить тесты github.com/cmdcolin/cra-webpack5-web-worker-example
3. @ColinD способ, которым я это сделал, заключался в том, чтобы добавлять плагин только в «тестовой» среде, что-то вроде этого в
babel.config.js
: gist.github.com/cmlenz/da2b8b9aa959d24b2d300a8a04a23727 . Не знаю о CRA4. потрясающе, спасибо! случайный другой вопрос: действительно ли он запускается в «worker», когда он использует jest таким образом?
5. Для тех, кто читает env вместо url, вы можете использовать следующий пакет: npmjs.com/package/babel-plugin-transform-vite-meta-env
Ответ №3:
У меня была такая же проблема, и я исправил ее с помощью плагина babel: search-and-replace
В моем коде я изменил все import.meta.url
с import_meta_url
И я добавляю плагин в конфигурацию babel, чтобы изменить его import.meta.url
при разработке и производстве и с помощью resolve(__dirname, 'workers')
Я смог это сделать, потому что он соответствовал всем моим случаям, если ваши import_meta_url
потребности должны быть заменены на разные укусы в разных сценариях, которые вам нужно будет использовать import_meta_url_1
import_meta_url_2
и т.д. например
наконец, мой babel.config.js
const { resolve } = require('path');
module.exports = (api) => {
api.cache.using(() => process.env.NODE_ENV);
if (process.env.NODE_ENV === 'test') {
return {
presets: ['next/babel'],
plugins: [
[
'search-and-replace',
{
rules: [
{
searchTemplateStrings: true,
search: 'import_meta_url',
replace: resolve(__dirname, 'workers'),
},
],
},
],
],
};
}
return {
presets: ['next/babel'],
plugins: [
[
'search-and-replace',
{
rules: [
{
searchTemplateStrings: true,
search: 'import_meta_url',
replace: 'import.meta.url',
},
],
},
],
],
};
};
GL
Комментарии:
1. Я использовал ту же конфигурацию в своем babel.config.js но это не сработало. Я получаю следующую ошибку
TypeError: Failed to construct 'URL': Invalid base URL
2.
search-and-replace
имя пакета было обновлено доbabel-plugin-search-and-replace
Ответ №4:
Я также столкнулся с этой проблемой. Я смог решить проблему, динамически вставляя правильное значение во время обработки babel. Вместо search-and-replace
предложенного плагина babel я использовал макрос codegen для babel.
// someCode.ts
import { dirname } from "path";
import { fileURLToPath } from "url";
import codegen from 'codegen.macro';
const DIRNAME = dirname(fileURLToPath(codegen`module.exports = process.env.NODE_ENV === "test" ? "{ADD VALID VALUE FOR TESTS}" : "import.meta.url"`));
export const x = 5;
Ответ №5:
Я думаю, что это здорово.
неофициальный.
вы можете использовать только babel-plugin-transform-vite-meta-env
плагин или использовать весь пресет и настроить конфигурацию, как показано ниже
{
"presets": [
[
"babel-preset-vite",
{
"env": true, // defaults to true
"glob": false // defaults to true
}
]
]
}
Комментарии:
1. Это сработало для меня. Единственная проблема заключалась в том, что при доступе к другим свойствам, отличным
env
от orglob
, напримерimport.meta.hot
, тогда это не сработало бы2. Этот плагин работает для меня!.
yarn add -D babel-plugin-transform-vite-meta-env
Затем установите, в babel.config.js добавить{ "plugins": ["babel-plugin-transform-vite-meta-env"] }
Ответ №6:
Я нашел решение, которое настолько нелепо, что я даже не поверил, когда оно сработало:
Создайте файл commonjs, например «currentPath.cjs», внутри него поместите что-то вроде:
module.exports = __dirname;
В вашем модуле используйте:
import * as currentPath from "./currentPath.cjs";
console.log(currentPath.default);
И наблюдайте, как происходит волшебство.
PS: используйте path.normalize(), прежде чем использовать его для чего-либо.
Ответ №7:
Внутри вашего tsconfig.json
изменения на
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node"
},
"include": ["./src/**/*.ts", "./src/**/*.js"]
}
Комментарии:
1. Это не сработало.
Ответ №8:
Я боролся с этим в течение Node.js , Экспресс, машинопись, Шутка, проект ts-jext. Для меня проблема заключалась в правильной настройке jest.config.json.
Я публикую свои файлы на случай, если это кому-то поможет.
PACKAGE.JSON
{
"name": "express-react",
"version": "0.0.0",
"private": true,
"main": "dist/index.js",
"type": "module",
"engines": {
"node": ">=14.0.0"
},
"scripts": {
"start:dev": "npm run build amp;amp; npm run dev",
"build": "npm run format amp;amp; npm run lint amp;amp; npm run tsc",
"start:prod": "npm run build amp;amp; node --experimental-specifier-resolution=node dist/index.js",
"start": "npm run start:prod",
"dev": "cross-env-shell DEBUG=express:* node --experimental-specifier-resolution=node --loader ts-node/esm ./src/index.ts",
"format": "prettier --write "**/*.ts"",
"lint": "eslint . --ext 'ts,json'",
"tsc": "tsc",
"test": "cross-env-shell TS_JEST_LOG=ts-jest.jog NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --verbose --testTimeout 20000 --coverage=true --reporters=default --json --debug --outputFile=./test/log.json",
"test:list": "jest --detectOpenHandles --coverage=true --listTests --debug"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"dotenv": "^16.0.0",
"express": "~4.16.1",
"express-fileupload": "^1.3.1",
"http-errors": "^1.6.3",
"into-stream": "^7.0.0",
"jest": "^28.1.0",
"morgan": "~1.9.1",
"multer": "^1.4.4",
"save-dev": "0.0.1-security",
"sharp": "^0.30.4",
"supertest": "^6.2.3",
"ts-jest": "^28.0.2",
"ts-node": "^10.7.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/jest": "^27.5.1",
"@types/supertest": "^2.0.12",
"@azure/storage-blob": "^12.9.0",
"@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.12",
"@types/debug": "^4.1.7",
"@types/express": "^4.16.1",
"@types/express-fileupload": "^1.2.2",
"@types/http-errors": "^1.8.2",
"@types/into-stream": "^3.1.1",
"@types/morgan": "^1.9.3",
"@types/multer": "^1.4.7",
"@types/node": "^17.0.31",
"@types/sharp": "^0.30.2",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"cross-env": "^7.0.3",
"eslint": "^8.15.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-node": "^11.1.0",
"nodemon": "^2.0.16",
"prettier": "^2.6.2",
"tslib": "^2.4.0",
"typescript": "^4.6.4"
}
}
TSCONFIG.JSON
{
"name": "express-react",
"version": "0.0.0",
"private": true,
"main": "dist/index.js",
"type": "module",
"engines": {
"node": ">=14.0.0"
},
"scripts": {
"start:dev": "npm run build amp;amp; npm run dev",
"build": "npm run format amp;amp; npm run lint amp;amp; npm run tsc",
"start:prod": "npm run build amp;amp; node --experimental-specifier-resolution=node dist/index.js",
"start": "npm run start:prod",
"dev": "cross-env-shell DEBUG=express:* node --experimental-specifier-resolution=node --loader ts-node/esm ./src/index.ts",
"format": "prettier --write "**/*.ts"",
"lint": "eslint . --ext 'ts,json'",
"tsc": "tsc",
"test": "cross-env-shell TS_JEST_LOG=ts-jest.jog NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --verbose --testTimeout 20000 --coverage=true --reporters=default --json --debug --outputFile=./test/log.json",
"test:list": "jest --detectOpenHandles --coverage=true --listTests --debug"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"dotenv": "^16.0.0",
"express": "~4.16.1",
"express-fileupload": "^1.3.1",
"http-errors": "^1.6.3",
"into-stream": "^7.0.0",
"jest": "^28.1.0",
"morgan": "~1.9.1",
"multer": "^1.4.4",
"save-dev": "0.0.1-security",
"sharp": "^0.30.4",
"supertest": "^6.2.3",
"ts-jest": "^28.0.2",
"ts-node": "^10.7.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/jest": "^27.5.1",
"@types/supertest": "^2.0.12",
"@azure/storage-blob": "^12.9.0",
"@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.12",
"@types/debug": "^4.1.7",
"@types/express": "^4.16.1",
"@types/express-fileupload": "^1.2.2",
"@types/http-errors": "^1.8.2",
"@types/into-stream": "^3.1.1",
"@types/morgan": "^1.9.3",
"@types/multer": "^1.4.7",
"@types/node": "^17.0.31",
"@types/sharp": "^0.30.2",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"cross-env": "^7.0.3",
"eslint": "^8.15.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-node": "^11.1.0",
"nodemon": "^2.0.16",
"prettier": "^2.6.2",
"tslib": "^2.4.0",
"typescript": "^4.6.4"
}
}
JEST.CONFIG.JS
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
clearMocks: true,
moduleFileExtensions: ["js", "ts", "json", "node"],
roots: ["./"],
testMatch: ["./**/*.test.ts"],
preset: "ts-jest",
testEnvironment: "node",
transform: {
"^. \.ts?$": "ts-jest",
},
transformIgnorePatterns: [
"./node_modules/",
"./dist/",
"./files/",
"./public/",
],
extensionsToTreatAsEsm: ['.ts'],
globals: {
"ts-jest": {
tsconfig: './tsconfig.json',
useESM: true
}
}
};
Комментарии:
1. Похоже, что ваш «TSCONFIG.JSON» на самом деле является повторением вашего package.json
Ответ №9:
1. измените свой tsconfig следующим образом
compilerOptions": {
"module": "esnext",
//... remaining options
}
2. добавьте ниже в jest.config
globals: {
"ts-jest": {
tsconfig: false,
useESM: true,
babelConfig: true,
plugins: ["babel-plugin-transform-vite-meta-env"],
},
},
transform: {
"^. \.(js|jsx|ts)$": "babel-jest",
}
`
3. Теперь он начнет выдавать ошибку Невозможно использовать import.meta Для решения этой проблемы добавьте ниже в babel config:
`
module.exports = function (api) {
api.cache(true);
const presets = [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript",
"@babel/preset-react",
];
return {
presets,
plugins: [
"@babel/plugin-transform-runtime",
"babel-plugin-transform-import-meta",
"babel-plugin-transform-vite-meta-env",
],
};
};`
@babel/plugin-transform-runtime, babel-plugin-transform-import-meta эти плагины помогут вам решить эту проблему
Ответ №10:
У меня такая же проблема с Jest и ESM, когда я пытался получить доступ __dirname
по-новому. Следует отметить, @babel
что в моем проекте его нет, но я думаю, что решение по-прежнему надежно.
Я пытался изменить module
и target
tsconfig.json
безрезультатно.
Кроме того, я изменил jest.config.cjs
файл, как показано ниже. По-прежнему нет результата.
{
...
testEnvironmentOptions: {
NODE_OPTIONS: '--experimental-vm-modules',
}
...
}
Наконец, обходной путь, который я нашел в файле pretter-plugin-xml package.json (который содержит import.meta.url в тестах), работает для меня.
Я перешел package.json:scripts:test
с jest
на явный jest.js позвоните:
{
...
"scripts": {
...
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
},
...
}
Это похоже на настройку NODE_OPTIONS="--experimental-vm-modules"
переменной среды, однако предоставленное решение кажется кроссплатформенным.
Мой tsconfig.json:compilerOptions
фрагмент:
{
...
"module": "NodeNext",
"target": "es2016",
...
}
The jest.config.cjs
:
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
// Without this option,
// error `The 'import.meta' meta-property is only allowed when ...` reveals
extensionsToTreatAsEsm: ['.ts'],
// No import resolution, otherwise
moduleNameMapper: {
'^(\.{1,2}/.*)\.js$': '$1',
},
// Process infinitely run, if the option is not set
transform: {
'^. \.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};