Related
Have got a successful jest/esm setup, however occasionally a module is released that specifies both a main key (for commonjs) and a module key (for ESM) in its package.json. This leads to jest errors, for example with the uuid module:
/repo/path/node_modules/uuid/dist/esm-browser/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { default as v1 } from './v1.js';
^^^^^^
SyntaxError: Unexpected token 'export'
I can see that dist/esm-browser/index.js is the file specified by the module key in package.json.
How can Jest w/ESM be configured to handle these cases, where stuff in node_modules is ESM?
Jest config:
{
"resetMocks": true,
"testEnvironment": "jsdom",
"testMatch": [
"**/src/**/*.(spec|test).[tj]s?(x)"
],
"preset": "ts-jest/presets/default-esm",
"extensionsToTreatAsEsm": [
".ts",
".tsx"
],
"globals": {
"ts-jest": {
"useESM": true
}
},
"globalSetup": "<rootDir>/jest/setup.cjs",
"globalTeardown": "<rootDir>/jest/teardown.cjs",
"watchPathIgnorePatterns": [
"<rootDir>/.tmp"
],
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/src/$1",
"^~components/(.*)$": "<rootDir>/src/components/$1",
"^~util/(.*)$": "<rootDir>/src/util/$1",
"^~types/(.*)$": "<rootDir>/src/types/$1"
}
}
If transformIgnorePatterns doesn't work for some reason, you can solve it with moduleNameMapper.
moduleNameMapper: {
// '^uuid$': '<rootDir>/node_modules/uuid/dist/index.js',
'^uuid$': require.resolve('uuid'),
}
I've had the same problem and it was fixed the same way as mentioned in this comment: https://github.com/nrwl/nx/issues/812#issuecomment-429420861 in my jest.config.js:
// list to add ESM to ignore
const esModules = ['uuid'].join('|');
// ...
module.exports = {
//...
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
// ...
};
I created an app within a monorepo (Lerna) using yarn workspace.
The architecture of the app is as follow:
my-monorepo
├── node_modules
├── packages
│ ├── package1(shared components)
│ ├── package2(other package consuming the shared components)
│ │ └── ./jest.config.js
├── package.json
The problem
the problem is jest is throwing the following error when trying to use package1 in package2 in any test, and I haven't found a way to fix it.
● Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest ca
nnot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transfor
m your files, ignoring "node_modules".
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io
/docs/en/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can spe
cify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option i
n 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/en/configuration.html
Details:
C:\Users\my-user\Desktop\my-monorepo\node_module
s\antd\es\date-picker\generatePicker\index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__fi
lename,global,jest){import _extends from "#babel/runtime/helpers/esm/exte
nds";
^^^^^^
According the error, I'm trying to import a file which Jest cannot parse, so the problem comes from the package1, so the first thing that comes to my mind is: maybe I'm doing something wrong in rollup in package1 and the final bundle comes in some format that jest doesn't understand...
Jest config
Jest config located in package2, where i want to consume package1:
// jest.config.js in package2
const config = {
roots: ['src'],
setupFilesAfterEnv: ['./jest.setup.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js'],
testPathIgnorePatterns: ['node_modules/'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testMatch: ['**/*.test.(ts|tsx)'],
moduleNameMapper: {
// Mocks out all these file formats when tests are run.
'\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'identity-obj-proxy',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
};
export default config;
Rollup config
This is the rollup configuration in package1:
// rollup.config.js
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import typescript from 'rollup-plugin-typescript2';
import postcss from 'rollup-plugin-postcss';
import copy from 'rollup-plugin-copy';
import json from '#rollup/plugin-json';
import svgr from '#svgr/rollup';
import { babel } from '#rollup/plugin-babel';
import { visualizer } from 'rollup-plugin-visualizer';
import pkg from './package.json';
export default {
input: 'src/index.tsx',
output: [
{
file: pkg.main,
format: 'cjs',
exports: 'named',
sourcemap: true,
},
{
file: pkg.module,
format: 'esm',
exports: 'named',
sourcemap: true,
},
],
plugins: [
peerDepsExternal({
includeDependencies: true,
}),
json(),
svgr(),
resolve({ extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] }),
commonjs({
include: /\**node_modules\**/,
}),
// UPDATE 3: Add babel
babel({
babelHelpers: 'bundled',
}),
typescript({
useTsconfigDeclarationDir: true,
exclude: ['*.d.ts', '**/*.d.ts', '**/*.test.tsx'],
rollupCommonJSResolveHack: true,
clean: true,
}),
postcss({
extensions: ['.css', '.less'],
use: {
less: { javascriptEnabled: true, modifyVars: {} },
},
}),
visualizer({ filename: 'stats-visualizer.html', gzipSize: true }),
copy({
targets: [
{
src: 'src/styles/themes/dark-variables.less',
dest: 'dist',
rename: 'theme/dark.less',
},
{
src: 'src/styles/themes/light-variables.less',
dest: 'dist',
rename: 'theme/light.less',
},
{
src: 'src/assets/icons',
dest: 'dist',
rename: 'assets/icons',
},
],
}),
],
};
UPDATE 1:
I've tried to use transform rules in jest.config.js as mentioned by Matt Carlota to transpile antdbut this doesn't work:
// jest.config.js
const config = {
// ... other jest settings
transform: {
'^.+\\.tsx?$': 'ts-jest',
'node_modules/antd/.+\\.(j|t)sx?$': 'ts-jest',
},
// I've tried with `antd/es` pattern too and doesn't work
transformIgnorePatterns: ['node_modules/(?!antd)'],
};
UPDATE 2:
Change manually antd/es by antd/lib in package1 resolve the problem temporarily, but there is one problem and that is that we are a large group of people working and it could be put as a convention to use only antd/lib but I feel it would be error prone.
every time someone forgets to use antd/lib and uses antd/en all tests break with the original error.
UPDATE 3:
Add babel config file and plugin in rollup configuration...
// babel.config.js
module.exports = {
plugins: [['import', { libraryName: 'antd', libraryDirectory: 'lib' }, 'antd']],
};
I've been having the same issue for the longest time and finally found a way.
The missing piece in my case was adding moduleNameMapper. A guide mentioned that here.
The solution doesn't require babel-jest.
jest.config.js
const path = require('path');
const { lstatSync, readdirSync } = require('fs');
// get listing of packages in the mono repo
const basePath = path.resolve(__dirname, '..', '..', 'packages');
const packages = readdirSync(basePath).filter((name) => {
return lstatSync(path.join(basePath, name)).isDirectory();
});
module.exports = {
preset: 'ts-jest',
verbose: true,
moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'ts', 'tsx'],
moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: {
...packages.reduce(
(acc, name) => ({
...acc,
[`#xyz/${name}(.*)$`]: `<rootDir>/../../packages/./${name}/src/$1`,
}),
{}
),
},
rootDir: './',
testRegex: '.spec.ts$',
transform: {
'^.+\\.(t)s$': 'ts-jest',
},
testEnvironment: 'node',
setupFilesAfterEnv: ['./jest.setup.ts'],
testMatch: null,
globals: {
'ts-jest': {
tsconfig: 'tsconfig.jest.json',
},
},
roots: ['<rootDir>'],
transformIgnorePatterns: [],
collectCoverage: false,
collectCoverageFrom: ['src/**/*.{js{,x},ts{,x}}', '!src/server/index.ts'],
};
tsconfig.json
{
"compilerOptions": {
"module": "esnext",
"target": "ES2019",
"lib": ["ES2019", "DOM"],
"noEmit": true,
"types": ["node", "jest"],
"rootDir": "./"
"paths": {
"#xyz/*": ["packages/*/src"]
},
},
"include": ["test/**/*.ts", "src/**/*.ts", "**/*.spec.ts"],
"references": [
{ "path": "../../packages/package1" },
]
}
package.json
...
scripts: {
"test": "NODE_ENV=production tsc --build ./tsconfig.jest.json && jest --env=node test --watch",
}
I use Vue 3 on Vite.js with Eslint + Airbnb config. Airbnb config has a rule eslint(import/no-unresolved), which is good, but Eslint doesn't know how to resolve alias path.
I want to use aliases for paths — example:
import TableComponent from '#/components/table/TableComponent.vue'˙
Environment is in plain JavaScript.
I managed to set up my vite.config.js so that the app can resolve paths like this:
import path from 'path';
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: [{
find: "#", replacement: path.resolve(__dirname, 'src')
},],
},
});
Vue app works like that and resolves the import path correctly, but Eslint keeps reporting the error: Unable to resolve path to module eslint(import/no-unresolved)
How and where can I tell Eslint how to resolve aliases?
I have tried:
install eslint-plugin-import eslint-import-resolver-alias --save-dev
// .eslintrc.js
// ...
extends: [
'eslint:recommended',
'plugin:import/recommended',
'airbnb-base',
'plugin:vue/vue3-strongly-recommended',
],
settings: {
'import/resolver': {
alias: {
map: [
['#', 'src'],
],
},
},
},
But that doesn't work.
EDIT:
Solved the issue, see the accepted answer if you're using plain JavaScript like I do.
If you're using TypeScript, see if Seyd's answer can help you.
this solves the issue in my TypeScript project.
npm install eslint-import-resolver-typescript
After eslint-import-resolver-typescript installation
{
// other configuration are omitted for brevity
settings: {
"import/resolver": {
typescript: {} // this loads <rootdir>/tsconfig.json to eslint
},
},
}
should be added to .eslintrc.js.
my tsconfig.json (remove unwanted settings)
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": false,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client", "node"],
"baseUrl": ".",
"paths": {
"#/*": ["src/*"]
},
"allowJs": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules"]
}
Check the discussion here:
In case someone runs into this problem, this works in my case*:
settings: {
'import/resolver': {
alias: {
map: [
['#', './src'],
],
},
},
},
*In my case, Vue's root is 'src' directory, while Eslint's is one level higher, so it needs the './src' path.
Huge thanks to #https://github.com/aladdin-add for the answer through the github question!
I had the same problem, and even I fixed the src path it still hat the issue. It did not work until I added extensions:
"settings": {
"import/resolver": {
"alias": {
"map": [
["#", "./src"]
],
"extensions": [".js",".jsx"] <--- HERE
}
}
},
1. Use & export aliases in vite.config.js
// vite.config.js
import { resolve } from 'path';
import { defineConfig } from 'vite';
export const aliases = {
'#': resolve(__dirname, './src'),
'#u': resolve(__dirname, './src/utils'),
};
export default () => defineConfig({
// ...
resolve: {
alias: aliases,
},
})
2. Create .eslintrc with ES Modules support. (Thanks to Morgan's answer)
2.1 npm i esm -D
2.2 Create sibling to .eslintrc.js — .eslintrc.esm.js.
2.3 Put your your ESLint config into .eslintrc.esm.js.
// .eslintrc.esm.js
export default {
root: true,
extends: ['#vue/airbnb', 'plugin:vue/recommended'],
// ...
}
2.4 Inside .eslintrc.js include this code:
const _require = require('esm')(module)
module.exports = _require('./.eslintrc.esm.js').default
3. Import, map & use aliases from vite.config.js in .eslintrc.esm.js
3.1 npm i eslint-import-resolver-alias -D
3.2 Inside .eslintrc.esm.js include following code:
// .eslintrc.esm.js
import { aliases } from './vite.config';
const mappedAliases = Object.entries(aliases).map((entry) => entry); // [[alias, path], [alias, path], ...]
export default {
// ...
settings: {
'import/resolver': {
alias: {
map: mappedAliases,
},
},
},
}
I can't for the life of me get this to work, I've tried everything above. Is there something else wrong with my file?
// .eslintrc.cjs
/* eslint-env node */
require("#rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
extends: [
"airbnb",
"plugin:vue/vue3-essential",
"eslint:recommended",
"#vue/eslint-config-prettier",
],
parserOptions: {
ecmaVersion: "latest",
},
// Using the accepted answer
settings: {
"import/resolver": {
alias: {
map: [["#", "./src"]],
},
},
},
};
** UPDATE **
The only way I was able to get this to work was using this: eslint-config-airbnb link here, the readme was particularly helpful.
npm add --dev #vue/eslint-config-airbnb #rushstack/eslint-patch
My .eslintrc.js is now:
/* eslint-env node */
require("#rushstack/eslint-patch/modern-module-resolution");
const path = require("node:path");
const createAliasSetting = require("#vue/eslint-config-airbnb/createAliasSetting");
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"#vue/eslint-config-airbnb", // <-- added
"eslint:recommended",
"#vue/eslint-config-prettier",
],
parserOptions: {
ecmaVersion: "latest",
},
rules: {
"import/no-unresolved": "error",
},
settings: {
...createAliasSetting({
"#": `${path.resolve(__dirname, "./src")}`,
}),
},
};
Huzzah!
In the README.md of eslint-plugin-import it is said:
Currently Node and webpack resolution have been implemented, but the resolvers are just npm packages, so third party packages are supported (and encouraged!).
Here you can see the list of the third party resolvers.
One that worked for me was this: eslint-import-resolver-custom-alias, and this is how I used it:
settings:
import/resolver:
eslint-import-resolver-custom-alias:
alias:
'#': './app/javascript'
extensions:
- '.js'
- '.vue'
For me the # had been set as an alias for app/javascript inside vite.config.ts, you should change this based on your project configurations.
in .eslintrc.js add
settings: {
'import/resolver': {
alias: {
map: [['#', './src/']],
extensions: ['.js', '.vue'],
},
},
},
In my jest.config.js file, I need to populate globals property. For populating the globals property I need to require local modules, as shown below:
const path = require('path')
const server = require('./server/cfg')
module.exports = {
rootDir: path.resolve(__dirname),
moduleFileExtensions: [
'js',
'json',
'vue',
'ts'
],
moduleNameMapper: {
'^#/(.*)$': '<rootDir>/src/$1'
},
transform: {
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
"^.+\\.(js|jsx)?$": "<rootDir>/node_modules/babel-jest",
"^.+\\.ts$": "<rootDir>/node_modules/ts-jest"
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
snapshotSerializers: [
"jest-serializer-vue"
],
setupFiles: [
"<rootDir>/globals.js"
],
testEnvironment: "jsdom",
globals: {
server: {
server
}
}
}
Whit this configuration, I get the following error:
Error: Cannot find module './server/cfg'
This is my folder structure
server/
cfg.ts
src/
jest.config.js
webpack.config.js
However, I can require node's built-in modules. I'm not able to figure out why it is happening. Any ideas on how I can overcome this?
Jest is initiated by node, not ts-node and it's not able to resolve the .ts file by default.
Probably adding setupFilesAfterEnv will help you.
jest.config.js
module.exports = {
// ... your config
setupFilesAfterEnv: [
"<rootDir>/environmentWithServer.ts`
]
}
environmentWithServer.ts
global.server = require('./server/cfg');
I am looking to be able to use webpack aliases to resolve imports when using jest, and optimally, reference the webpack.aliases to avoid duplication.
Jest conf:
"jest": {
"modulePaths": ["src"],
"moduleDirectories": ["node_modules"],
"moduleNameMapper": {
"^#shared$": "<rootDir>/shared/",
"^#components$": "<rootDir>/shared/components/"
}
},
Webpack aliases:
exports.aliases = {
'#shared': path.resolve(paths.APP_DIR, 'shared'),
'#components': path.resolve(paths.APP_DIR, 'shared/components'),
};
Imports:
import Ordinal from '#shared/utils/Ordinal.jsx';
import Avatar from '#components/common/Avatar.jsx';
For some reason the # causes issues, so when removed (in both alias and import), it can find shared but components still cannot be resolved.
FAIL src/shared/components/test/Test.spec.jsx
● Test suite failed to run
Cannot find module '#shared/utils/Ordinal.jsx' from 'Test.jsx'
I have tried using jest-webpack-alias, babel-plugin-module-resolver and the Jest/Webpack docs
This seems to have been fixed.
Below is a working setup:
Versions
"jest": "~20.0.4"
"webpack": "^3.5.6"
package.json
"jest": {
"moduleNameMapper": {
"^#root(.*)$": "<rootDir>/src$1",
"^#components(.*)$": "<rootDir>/src/components$1",
}
}
webpack.shared.js
const paths = {
APP_DIR: path.resolve(__dirname, '..', 'src'),
};
exports.resolveRoot = [paths.APP_DIR, 'node_modules'];
exports.aliases = {
'#root': path.resolve(paths.APP_DIR, ''),
'#components': path.resolve(paths.APP_DIR, 'components'),
};
Since I had the same problem before I read again, and this time more carefully the documentation. Correct config should be:
"jest": {
"moduleNameMapper": {
"^#shared(.*)$": "<rootDir>/shared$1",
"^#components(.*)$": "<rootDir>/shared/components$1"
}
},
Using: "jest": "^26.5.3", and "webpack": "4.41.5",
I was able to properly match my webpack/typescript aliases in the jest.config.js with this pattern:
Webpack config:
module.exports = {
// the rest of your config
resolve: {
alias: {
'components': path.resolve(__dirname, 'js/app/components'),
'modules': path.resolve(__dirname, 'js/app/modules'),
'types': path.resolve(__dirname, 'js/types'),
'hooks': path.resolve(__dirname, 'js/app/hooks'),
'reducers': path.resolve(__dirname, 'js/app/reducers'),
'__test-utils__': path.resolve(__dirname, 'js/app/__test-utils__')
}
},
}
Jest.config.js:
moduleNameMapper: {
'^types/(.*)$': '<rootDir>/js/types/$1',
'^components/(.*)$': '<rootDir>/js/app/components/$1',
'^modules/(.*)$': '<rootDir>/js/app/modules/$1',
'^hooks/(.*)$': '<rootDir>/js/app/hooks/$1',
'^reducers/(.*)$': '<rootDir>/js/app/reducers/$1',
'^__test-utils__/(.)$': '<rootDir>/js/app/__test-utils__/$1'
}
Here's an explanation of the symbols:
(.*)$: capture whatever comes after the exact match (the directory)
$1: map it to this value in the directory I specify.
and tsconfig.json:
"paths": {
"config": ["config/dev", "config/production"],
"components/*": ["js/app/components/*"],
"modules/*": ["js/app/modules/*"],
"types/*": ["js/types/*"],
"hooks/*": ["js/app/hooks/*"],
"reducers/*": ["js/app/reducers/*"],
"__test-utils__/*": ["js/app/__test-utils__/*"]
}
For anyone using # as the root of their modules, you have to be more specific since other libs can use the # in node modules.
moduleNameMapper: {
"^#/(.*)$": "<rootDir>/src/$1"
},
It translates to "anything that matches #/ should be sent to <rootDir>/src/<rest of the path>
FWIW, Try switching the alias order, keep the more specific up and less specific down, e.g.
"moduleNameMapper": {
"^#components$": "<rootDir>/shared/components/",
"^#shared$": "<rootDir>/shared/"
}
This is a real insanity but when I try to map module in package.json like this:
"jest": {
"moduleNameMapper": {
'#root/(.*)': '<rootDir>/../$1'
}
}
It doesn't work.
But when I do the same in jest.config.js it works:
module.exports = {
moduleNameMapper: {
'#root/(.*)': '<rootDir>/../$1'
}
}
None of the suggested answers worked for me.
Was able to solve it with the following pattern:
Jest version 25+
moduleNameMapper: {
'^Screens(.*)': '<rootDir>/src/screens/$1',
'^Components(.*)': '<rootDir>/src/components/$1',
'^Assets(.*)': '<rootDir>/src/assets/$1',
'^Services/(.*)': '<rootDir>/src/services/$1',
},
this solution works well for me:
moduleNameMapper: {
'^Screens(.*)': '<rootDir>/src/screens/$1',
'^Components(.*)': '<rootDir>/src/components/$1',
'^Assets(.*)': '<rootDir>/src/assets/$1',
'^Services/(.*)': '<rootDir>/src/services/$1',
},
Assume your webpack aliases lies in webpack.yml in "resolved_paths" block.
To add same aliases in jest, if your jest config is in package.json, use moduleDirectories:
"jest":{
"moduleDirectories":[
".",
"<rootDir>/shared/components/",
"<rootDir>/shared/",
"node_modules"
]
}