Skip to content

Commit

Permalink
feat: add useESM option to pathsToModuleNameMapper options (#3792)
Browse files Browse the repository at this point in the history
  • Loading branch information
jjangga0214 committed Sep 25, 2022
1 parent 12a90d3 commit eabe906
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 22 deletions.
4 changes: 2 additions & 2 deletions e2e/__tests__/native-esm-ts.test.ts
Expand Up @@ -8,7 +8,7 @@ onNodeVersions('>=12.16.0', () => {
})

expect(exitCode).toBe(0)
expect(json.numTotalTests).toBe(3)
expect(json.numPassedTests).toBe(3)
expect(json.numTotalTests).toBe(4)
expect(json.numPassedTests).toBe(4)
})
})
7 changes: 6 additions & 1 deletion e2e/native-esm-ts/__tests__/native-esm-ts.spec.ts
@@ -1,6 +1,7 @@
import { test, expect } from '@jest/globals'

import { double } from '../double'
import { double } from '../double.js'
import { quadruple } from '../quadruple/index.js'
import { triple } from '../triple.mjs'

test('double', () => {
Expand All @@ -11,6 +12,10 @@ test('triple', () => {
expect(triple(2)).toBe(6)
})

test('quadruple', () => {
expect(quadruple(2)).toBe(8)
})

test('import.meta', () => {
expect(typeof import.meta.url).toBe('string')
})
7 changes: 4 additions & 3 deletions e2e/native-esm-ts/jest-isolated.config.js
@@ -1,7 +1,8 @@
import config from './jest.config.js'

/** @type {import('../../dist').JestConfigWithTsJest} */
module.exports = {
extensionsToTreatAsEsm: ['.ts'],
resolver: '<rootDir>/mjs-resolver.ts',
export default {
...config,
transform: {
'^.+\\.m?tsx?$': [
'<rootDir>/../../legacy.js',
Expand Down
23 changes: 23 additions & 0 deletions e2e/native-esm-ts/jest.config.js
@@ -0,0 +1,23 @@
import { pathsToModuleNameMapper } from '../../dist/index.js'
import { createRequire } from 'module'

const require = createRequire(import.meta.url)
const tsConfig = require('./tsconfig.json')

/** @type {import('../../dist').JestConfigWithTsJest} */
export default {
extensionsToTreatAsEsm: ['.ts'],
resolver: '<rootDir>/mjs-resolver.ts',
moduleNameMapper: pathsToModuleNameMapper(tsConfig.compilerOptions.paths, {
prefix: '<rootDir>',
useESM: true,
}),
transform: {
'^.+\\.m?tsx?$': [
'<rootDir>/../../legacy.js',
{
useESM: true,
},
],
},
}
9 changes: 0 additions & 9 deletions e2e/native-esm-ts/package.json
Expand Up @@ -2,14 +2,5 @@
"type": "module",
"devDependencies": {
"@jest/globals": "^29.0.3"
},
"jest": {
"extensionsToTreatAsEsm": [".ts"],
"resolver": "<rootDir>/mjs-resolver.ts",
"transform": {
"^.+\\.m?tsx?$": ["<rootDir>/../../legacy.js", {
"useESM": true
}]
}
}
}
2 changes: 2 additions & 0 deletions e2e/native-esm-ts/quadruple/calculate.ts
@@ -0,0 +1,2 @@
const calculate = (x: number) => x * 4
export default calculate
1 change: 1 addition & 0 deletions e2e/native-esm-ts/quadruple/index.ts
@@ -0,0 +1 @@
export { default as quadruple } from '@quadruple/calculate.js'
5 changes: 4 additions & 1 deletion e2e/native-esm-ts/tsconfig.json
Expand Up @@ -3,6 +3,9 @@
"module": "Node16",
"target": "ESNext",
"moduleResolution": "Node16",
"esModuleInterop": true
"esModuleInterop": true,
"paths": {
"@quadruple/*": ["quadruple/*"]
}
}
}
27 changes: 27 additions & 0 deletions src/config/paths-to-module-name-mapper.spec.ts
Expand Up @@ -39,6 +39,33 @@ describe('pathsToModuleNameMapper', () => {
`)
})

test('should add `js` extension to resolved config with useESM: true', () => {
expect(pathsToModuleNameMapper(tsconfigMap, { useESM: true })).toEqual({
/**
* Why not using snapshot here?
* Because the snapshot does not keep the property order, which is important for jest.
* A pattern ending with `\\.js` should appear before another pattern without the extension does.
*/
'^log$': 'src/utils/log',
'^server$': 'src/server',
'^client$': ['src/client', 'src/client/index'],
'^util/(.*)\\.js$': 'src/utils/$1',
'^util/(.*)$': 'src/utils/$1',
'^api/(.*)\\.js$': 'src/api/$1',
'^api/(.*)$': 'src/api/$1',
'^test/(.*)\\.js$': 'test/$1',
'^test/(.*)$': 'test/$1',
'^mocks/(.*)\\.js$': 'test/mocks/$1',
'^mocks/(.*)$': 'test/mocks/$1',
'^test/(.*)/mock\\.js$': ['test/mocks/$1', 'test/__mocks__/$1'],
'^test/(.*)/mock$': ['test/mocks/$1', 'test/__mocks__/$1'],
'^@foo\\-bar/common$': '../common/dist/library',
'^@pkg/(.*)\\.js$': './packages/$1',
'^@pkg/(.*)$': './packages/$1',
'^(\\.{1,2}/.*)\\.js$': '$1',
})
})

test.each(['<rootDir>/', 'foo'])('should convert tsconfig mapping with given prefix', (prefix) => {
expect(pathsToModuleNameMapper(tsconfigMap, { prefix })).toMatchSnapshot(prefix)
})
Expand Down
19 changes: 13 additions & 6 deletions src/config/paths-to-module-name-mapper.ts
Expand Up @@ -16,11 +16,10 @@ const logger = rootLogger.child({ [LogContexts.namespace]: 'path-mapper' })

export const pathsToModuleNameMapper = (
mapping: TsPathMapping,
{ prefix = '' }: { prefix: string } = Object.create(null),
{ prefix = '', useESM = false }: { prefix?: string; useESM?: boolean } = {},
): JestPathMapping => {
const jestMap: JestPathMapping = {}
for (const fromPath of Object.keys(mapping)) {
let pattern: string
const toPaths = mapping[fromPath]
// check that we have only one target path
if (toPaths.length === 0) {
Expand All @@ -37,8 +36,8 @@ export const pathsToModuleNameMapper = (

return `${enrichedPrefix}${target}`
})
pattern = `^${escapeRegex(fromPath)}$`
jestMap[pattern] = paths.length === 1 ? paths[0] : paths
const cjsPattern = `^${escapeRegex(fromPath)}$`
jestMap[cjsPattern] = paths.length === 1 ? paths[0] : paths
} else if (segments.length === 2) {
const paths = toPaths.map((target) => {
const enrichedTarget =
Expand All @@ -47,12 +46,20 @@ export const pathsToModuleNameMapper = (

return `${enrichedPrefix}${enrichedTarget.replace(/\*/g, '$1')}`
})
pattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex(segments[1])}$`
jestMap[pattern] = paths.length === 1 ? paths[0] : paths
if (useESM) {
const esmPattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex(segments[1])}\\.js$`
jestMap[esmPattern] = paths.length === 1 ? paths[0] : paths
}
const cjsPattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex(segments[1])}$`
jestMap[cjsPattern] = paths.length === 1 ? paths[0] : paths
} else {
logger.warn(interpolate(Errors.NotMappingMultiStarPath, { path: fromPath }))
}
}

if (useESM) {
jestMap['^(\\.{1,2}/.*)\\.js$'] = '$1'
}

return jestMap
}
6 changes: 6 additions & 0 deletions website/docs/getting-started/paths-mapping.md
Expand Up @@ -94,3 +94,9 @@ const jestConfig: JestConfigWithTsJest = {

export default jestConfig
```

With extra options as 2nd argument:

- `prefix`: append prefix to each of mapped config in the result
- `useESM`: when using `type: module` in `package.json`, TypeScript enforces users to have explicit `js` extension when importing
a `ts` file. This option is to help `pathsToModuleNameMapper` to create a config to suit with this scenario.

0 comments on commit eabe906

Please sign in to comment.