diff --git a/README.md b/README.md index e725425892..0c4be82539 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ -It supports all features of TypeScript including type-checking. [Read more about Babel7 + `preset-typescript` **vs** TypeScript (and `ts-jest`)](https://kulshekhar.github.io/ts-jest/user/babel7-or-ts). +It supports all features of TypeScript including type-checking. [Read more about Babel7 + `preset-typescript` **vs** TypeScript (and `ts-jest`)](https://kulshekhar.github.io/ts-jest/docs/babel7-or-ts). --- diff --git a/docs/docs/presets.md b/docs/docs/presets.md index 79545db037..74c371821a 100644 --- a/docs/docs/presets.md +++ b/docs/docs/presets.md @@ -3,15 +3,18 @@ id: presets title: Presets --- -### The 3 presets +### The presets -`ts-jest` comes with 3 presets, covering most of the project's base configuration: +`ts-jest` comes with several presets, covering most of the project's base configuration: | Preset name | Description | |---|---| -| `ts-jest/presets/default`
or `ts-jest` | `ts-jest` will take care of `.ts` and `.tsx` files only, leaving JavaScript files as-is. | -| `ts-jest/presets/js-with-ts` | TypeScript and JavaScript files (`.ts`, `.tsx`, `.js` and `.jsx`) will be handled by `ts-jest`.
You'll need to set `allowJs` to `true` in your `tsconfig.json` file. | -| `ts-jest/presets/js-with-babel` | TypeScript files will be handled by `ts-jest`, and JavaScript files will be handled by `babel-jest`. | +| `ts-jest/presets/default`
or `ts-jest` | TypeScript files (`.ts`, `.tsx`) will be transformed by `ts-jest` to **CommonJS** syntax, leaving JavaScript files (`.js`, `jsx`) as-is. | +| `ts-jest/presets/default-esm`
| TypeScript files (`.ts`, `.tsx`) will be transformed by `ts-jest` to **ESM** syntax, leaving JavaScript files (`.js`, `jsx`) as-is. | +| `ts-jest/presets/js-with-ts` | TypeScript and JavaScript files (`.ts`, `.tsx`, `.js`, `.jsx`) will be transformed by `ts-jest` to **CommonJS** syntax.
You'll need to set `allowJs` to `true` in your `tsconfig.json` file. | +| `ts-jest/presets/js-with-ts-esm` | TypeScript and JavaScript files (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`) will be transformed by `ts-jest` to **ESM** syntax.
You'll need to set `allowJs` to `true` in your `tsconfig.json` file. | +| `ts-jest/presets/js-with-babel` | TypeScript files (`.ts`, `.tsx`) will be transformed by `ts-jest` to **CommonJS** syntax, and JavaScript files (`.js`, `jsx`) will be transformed by `babel-jest`. | +| `ts-jest/presets/js-with-babel-esm` | TypeScript files (`.ts`, `.tsx`) will be transformed by `ts-jest` to **ESM** syntax, and JavaScript files (`.js`, `jsx`, `.mjs`) will be transformed by `babel-jest`. | ### Basic usage @@ -27,7 +30,7 @@ module.exports = { }; ``` -```js +```json5 // OR package.json { // [...] diff --git a/e2e/__cases__/presets/ts-jest-presets.spec.ts b/e2e/__cases__/presets/ts-jest-presets.spec.ts index a3359d9fc0..0012429072 100644 --- a/e2e/__cases__/presets/ts-jest-presets.spec.ts +++ b/e2e/__cases__/presets/ts-jest-presets.spec.ts @@ -1,9 +1,41 @@ // preset and utils should work all the time import * as presets from 'ts-jest/presets' +import { JS_EXT_TO_TREAT_AS_ESM, TS_EXT_TO_TREAT_AS_ESM } from 'ts-jest/dist/constants' test('presets', () => { - const presetKeys = ['transform'] - expect(Object.keys(presets.defaults)).toEqual(presetKeys) - expect(Object.keys(presets.jsWithBabel)).toEqual(presetKeys) - expect(Object.keys(presets.jsWithTs)).toEqual(presetKeys) + expect(presets.defaults).toEqual({ + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + }) + expect(presets.defaultsESM).toEqual({ + extensionsToTreatAsEsm: [...TS_EXT_TO_TREAT_AS_ESM], + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + }) + expect(presets.jsWithTs).toEqual({ + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + }) + expect(presets.jsWithTsESM).toEqual({ + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + transform: { + '^.+\\.m?[tj]sx?$': 'ts-jest', + }, + }) + expect(presets.jsWithBabel).toEqual({ + transform: { + '^.+\\.tsx?$': 'ts-jest', + '^.+\\.jsx?$': 'babel-jest', + }, + }) + expect(presets.jsWithBabelESM).toEqual({ + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + transform: { + '^.+\\.tsx?$': 'ts-jest', + '^.+\\.m?[j]sx?$': 'babel-jest', + }, + }) }) diff --git a/e2e/__cases__/ts-jest-checks/index.spec.ts b/e2e/__cases__/ts-jest-checks/index.spec.ts deleted file mode 100644 index afda5cec8a..0000000000 --- a/e2e/__cases__/ts-jest-checks/index.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { readFileSync } from 'fs' -import { ConfigSet } from 'ts-jest/dist/config/config-set' - -it('should have digest and versions', () => { - const cs = new ConfigSet(Object.create(null)) - expect(cs.tsJestDigest).toHaveLength(40) - expect(cs.tsJestDigest).toBe(readFileSync(require.resolve('ts-jest/.ts-jest-digest'), 'utf8')) -}) diff --git a/e2e/__tests__/__snapshots__/jest-presets.test.ts.snap b/e2e/__tests__/__snapshots__/jest-presets.test.ts.snap deleted file mode 100644 index 1429b99dc6..0000000000 --- a/e2e/__tests__/__snapshots__/jest-presets.test.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ts-jest/presets/js-with-babel should pass using template "with-babel-7" 1`] = ` - √ jest - ↳ exit code: 0 - ===[ STDOUT ]=================================================================== - - ===[ STDERR ]=================================================================== - PASS ./main.spec.js - √ spread - - Test Suites: 1 passed, 1 total - Tests: 1 passed, 1 total - Snapshots: 0 total - Time: XXs - Ran all test suites. - ================================================================================ -`; - -exports[`ts-jest/presets/js-with-babel should pass using template "with-babel-7-string-config" 1`] = ` - √ jest - ↳ exit code: 0 - ===[ STDOUT ]=================================================================== - - ===[ STDERR ]=================================================================== - PASS ./main.spec.js - √ spread - - Test Suites: 1 passed, 1 total - Tests: 1 passed, 1 total - Snapshots: 0 total - Time: XXs - Ran all test suites. - ================================================================================ -`; diff --git a/e2e/__tests__/jest-presets.test.ts b/e2e/__tests__/jest-presets.test.ts index d8d7667f44..b1ded2aaf2 100644 --- a/e2e/__tests__/jest-presets.test.ts +++ b/e2e/__tests__/jest-presets.test.ts @@ -1,34 +1,10 @@ -import { allValidPackageSets, PackageSets } from '../__helpers__/templates' +import { PackageSets } from '../__helpers__/templates' import { configureTestCase } from '../__helpers__/test-case' -// 'ts-jest' is tested in almost all test cases -// 'ts-jest/presets/default' is an alias of the above -// 'ts-jest/presets/js-with-ts' is tested in allow-js.test.ts - -describe('ts-jest/presets/js-with-babel', () => { - const testCase = configureTestCase('preset-with-babel', { jestConfig: { preset: 'ts-jest/presets/js-with-babel' } }) - - testCase.runWithTemplates([PackageSets.default], 1, (runTest, { testLabel }) => { - it(testLabel, () => { - const result = runTest() - expect(result.status).toBe(1) - expect(result.stderr).toMatch(/(Couldn't|Cannot) find (preset|module) ["']@babel\/preset-env["']/) - }) - }) - - testCase.runWithTemplates([PackageSets.babel7, PackageSets.babel7StringConfig], 0, (runTest, { testLabel }) => { - it(testLabel, () => { - const result = runTest() - expect(result.status).toBe(0) - expect(result).toMatchSnapshot() - }) - }) -}) - describe('ts-jest all presets', () => { const testCase = configureTestCase('presets') - testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { testLabel }) => { + testCase.runWithTemplates([PackageSets.default], 0, (runTest, { testLabel }) => { it(testLabel, () => { const result = runTest() expect(result.status).toBe(0) diff --git a/e2e/__tests__/ts-jest-checks.test.ts b/e2e/__tests__/ts-jest-checks.test.ts deleted file mode 100644 index 99a0554378..0000000000 --- a/e2e/__tests__/ts-jest-checks.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { allValidPackageSets } from '../__helpers__/templates' -import { configureTestCase } from '../__helpers__/test-case' - -describe('ts-jest internal checks test', () => { - const testCase = configureTestCase('ts-jest-checks') - - testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { testLabel }) => { - it(testLabel, () => { - const result = runTest() - expect(result.status).toBe(0) - }) - }) -}) diff --git a/presets/default-esm/jest-preset.js b/presets/default-esm/jest-preset.js new file mode 100644 index 0000000000..caa8bf6070 --- /dev/null +++ b/presets/default-esm/jest-preset.js @@ -0,0 +1 @@ +module.exports = require('..').defaultsESM diff --git a/presets/index.js b/presets/index.js index 2d67bb225e..07eedbbca6 100644 --- a/presets/index.js +++ b/presets/index.js @@ -1,20 +1,32 @@ +const { JS_EXT_TO_TREAT_AS_ESM, TS_EXT_TO_TREAT_AS_ESM } = require('../dist/constants') const { createJestPreset } = require('../dist/presets/create-jest-preset') module.exports = { get defaults() { return createJestPreset() }, + get defaultsESM() { + return createJestPreset(false, { extensionsToTreatAsEsm: TS_EXT_TO_TREAT_AS_ESM }) + }, get jsWithTs() { - return createJestPreset({ allowJs: true }) + return createJestPreset(true) + }, + get jsWithTsESM() { + return createJestPreset(true, { extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM] }) }, get jsWithBabel() { - return createJestPreset( - { allowJs: false }, - { - transform: { - '^.+\\.jsx?$': 'babel-jest', - }, - } - ) + return createJestPreset(false, { + transform: { + '^.+\\.jsx?$': 'babel-jest', + }, + }) + }, + get jsWithBabelESM() { + return createJestPreset(false, { + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + transform: { + '^.+\\.m?[j]sx?$': 'babel-jest', + }, + }) }, } diff --git a/presets/js-with-babel-esm/jest-preset.js b/presets/js-with-babel-esm/jest-preset.js new file mode 100644 index 0000000000..9598209f1d --- /dev/null +++ b/presets/js-with-babel-esm/jest-preset.js @@ -0,0 +1 @@ +module.exports = require('..').jsWithBabelESM diff --git a/presets/js-with-ts-esm/jest-preset.js b/presets/js-with-ts-esm/jest-preset.js new file mode 100644 index 0000000000..5573ca60e2 --- /dev/null +++ b/presets/js-with-ts-esm/jest-preset.js @@ -0,0 +1 @@ +module.exports = require('..').jsWithTsESM diff --git a/src/constants.ts b/src/constants.ts index 1c19aeb993..1c33a9d9e4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,6 +2,9 @@ export const LINE_FEED = '\n' export const TS_TSX_REGEX = /\.tsx?$/ export const JS_JSX_REGEX = /\.jsx?$/ export const DECLARATION_TYPE_EXT = '.d.ts' +// `extensionsToTreatAsEsm` only accepts `.ts`, `.tsx` and `.jsx`. `.js` and `.mjs` will throw error +export const TS_EXT_TO_TREAT_AS_ESM = ['.ts', '.tsx'] +export const JS_EXT_TO_TREAT_AS_ESM = ['.jsx'] /** * @internal * See https://jestjs.io/docs/en/configuration#testmatch-arraystring diff --git a/src/presets/__snapshots__/create-jest-preset.spec.ts.snap b/src/presets/__snapshots__/create-jest-preset.spec.ts.snap new file mode 100644 index 0000000000..2f8d12dbd7 --- /dev/null +++ b/src/presets/__snapshots__/create-jest-preset.spec.ts.snap @@ -0,0 +1,76 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`create-jest-preset should return correct preset 1`] = ` +Object { + "transform": Object { + "^.+\\\\.tsx?$": "ts-jest", + }, +} +`; + +exports[`create-jest-preset should return correct preset 2`] = ` +Object { + "transform": Object { + "^.+\\\\.tsx?$": "ts-jest", + }, +} +`; + +exports[`create-jest-preset should return correct preset 3`] = ` +Object { + "transform": Object { + "^.+\\\\.[tj]sx?$": "ts-jest", + }, +} +`; + +exports[`create-jest-preset should return correct preset 4`] = ` +Object { + "transform": Object { + "^.+\\\\.[tj]sx?$": "ts-jest", + }, +} +`; + +exports[`create-jest-preset should return correct preset 5`] = ` +Object { + "transform": Object { + "^.+\\\\.tsx?$": "ts-jest", + }, +} +`; + +exports[`create-jest-preset should return correct preset 6`] = ` +Object { + "moduleFileExtensions": Array [ + "bar", + ], + "testMatch": Array [ + "foo", + ], + "transform": Object { + "^.+\\\\.tsx?$": "ts-jest", + "foo": "bar", + }, +} +`; + +exports[`create-jest-preset should return correct preset 7`] = ` +Object { + "extensionsToTreatAsEsm": Array [ + ".jsx", + ".ts", + ".tsx", + ], + "moduleFileExtensions": Array [ + "bar", + ], + "testMatch": Array [ + "foo", + ], + "transform": Object { + "^.+\\\\.m?[tj]sx?$": "ts-jest", + "foo": "bar", + }, +} +`; diff --git a/src/presets/create-jest-preset.spec.ts b/src/presets/create-jest-preset.spec.ts index a906c9e46c..49d8b7530c 100644 --- a/src/presets/create-jest-preset.spec.ts +++ b/src/presets/create-jest-preset.spec.ts @@ -1,47 +1,46 @@ import { createJestPreset } from './create-jest-preset' +import { JS_EXT_TO_TREAT_AS_ESM, TS_EXT_TO_TREAT_AS_ESM } from '../constants' describe('create-jest-preset', () => { - it('should return correct defaults when allowJs is false or not set', () => { - const withoutJs = { - transform: { - '^.+\\.tsx?$': 'ts-jest', - }, - } - expect(createJestPreset()).toEqual(withoutJs) - expect(createJestPreset({ allowJs: false })).toEqual(withoutJs) - }) + const baseExtraOptions = { + testMatch: ['foo'], + moduleFileExtensions: ['bar'], + transform: { foo: 'bar' }, + } - it('should return correct defaults when allowJs is true', () => { - expect(createJestPreset({ allowJs: true })).toEqual({ - transform: { - '^.+\\.[tj]sx?$': 'ts-jest', + test.each([ + { + allowJs: undefined, + extraOptions: undefined, + }, + { + allowJs: false, + extraOptions: undefined, + }, + { + allowJs: true, + extraOptions: undefined, + }, + { + allowJs: true, + extraOptions: {}, + }, + { + allowJs: false, + extraOptions: {}, + }, + { + allowJs: false, + extraOptions: baseExtraOptions, + }, + { + allowJs: true, + extraOptions: { + ...baseExtraOptions, + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], }, - }) - }) - - it('should be able to use a base config', () => { - expect(createJestPreset(undefined, {})).toMatchInlineSnapshot(` -Object { - "transform": Object { - "^.+\\\\.tsx?$": "ts-jest", - }, -} -`) - expect( - createJestPreset(undefined, { testMatch: ['foo'], moduleFileExtensions: ['bar'], transform: { foo: 'bar' } }), - ).toMatchInlineSnapshot(` -Object { - "moduleFileExtensions": Array [ - "bar", - ], - "testMatch": Array [ - "foo", - ], - "transform": Object { - "^.+\\\\.tsx?$": "ts-jest", - "foo": "bar", - }, -} -`) + }, + ])('should return correct preset', (data) => { + expect(createJestPreset(data.allowJs, data.extraOptions)).toMatchSnapshot() }) }) diff --git a/src/presets/create-jest-preset.ts b/src/presets/create-jest-preset.ts index 0b48c79eac..28be1528af 100644 --- a/src/presets/create-jest-preset.ts +++ b/src/presets/create-jest-preset.ts @@ -4,24 +4,23 @@ import { rootLogger } from '../utils/logger' const logger = rootLogger.child({ namespace: 'jest-preset' }) -export type TsJestPresets = Pick +export type TsJestPresets = Pick< + Config.InitialOptions, + 'extensionsToTreatAsEsm' | 'moduleFileExtensions' | 'transform' | 'testMatch' +> -interface CreateJestPresetOptions { - allowJs?: boolean -} - -export function createJestPreset( - { allowJs = false }: CreateJestPresetOptions = {}, - from: Config.InitialOptions = {}, -): TsJestPresets { +export function createJestPreset(allowJs = false, extraOptions: Config.InitialOptions = {}): TsJestPresets { logger.debug({ allowJs }, 'creating jest presets', allowJs ? 'handling' : 'not handling', 'JavaScript files') + const { extensionsToTreatAsEsm, moduleFileExtensions, testMatch } = extraOptions + return { + ...(extensionsToTreatAsEsm ? { extensionsToTreatAsEsm } : undefined), + ...(moduleFileExtensions ? { moduleFileExtensions } : undefined), transform: { - ...from.transform, - [allowJs ? '^.+\\.[tj]sx?$' : '^.+\\.tsx?$']: 'ts-jest', + ...extraOptions.transform, + [allowJs ? (extensionsToTreatAsEsm?.length ? '^.+\\.m?[tj]sx?$' : '^.+\\.[tj]sx?$') : '^.+\\.tsx?$']: 'ts-jest', }, - ...(from.testMatch ? { testMatch: from.testMatch } : undefined), - ...(from.moduleFileExtensions ? { moduleFileExtensions: from.moduleFileExtensions } : undefined), + ...(testMatch ? { testMatch } : undefined), } } diff --git a/src/ts-jest-transformer.ts b/src/ts-jest-transformer.ts index da267c47af..5faf00fddb 100644 --- a/src/ts-jest-transformer.ts +++ b/src/ts-jest-transformer.ts @@ -12,15 +12,15 @@ import { parse, stringify } from './utils/json' import { JsonableValue } from './utils/jsonable-value' import { rootLogger } from './utils/logger' import { Errors, interpolate } from './utils/messages' -import type { CompilerInstance } from './types' +import type { TsJestProjectConfig, TransformOptionsTsJest } from './types' import { sha1 } from './utils/sha1' import { VersionCheckers } from './utils/version-checkers' interface CachedConfigSet { configSet: ConfigSet - jestConfig: JsonableValue + jestConfig: JsonableValue transformerCfgStr: string - compiler: CompilerInstance + compiler: TsJestCompiler } export interface DepGraphInfo { @@ -40,7 +40,7 @@ export class TsJestTransformer implements Transformer { /** * @internal */ - private _compiler!: CompilerInstance + private _compiler!: TsJestCompiler protected readonly _logger: Logger protected _tsResolvedModulesCachePath: string | undefined protected _transformCfgStr!: string @@ -113,7 +113,11 @@ export class TsJestTransformer implements Transformer { /** * @public */ - process(fileContent: string, filePath: Config.Path, transformOptions: TransformOptions): TransformedSource | string { + process( + fileContent: string, + filePath: Config.Path, + transformOptions: TransformOptionsTsJest, + ): TransformedSource | string { this._logger.debug({ fileName: filePath, transformOptions }, 'processing', filePath) let result: string | TransformedSource @@ -177,7 +181,7 @@ export class TsJestTransformer implements Transformer { * * @public */ - getCacheKey(fileContent: string, filePath: string, transformOptions: TransformOptions): string { + getCacheKey(fileContent: string, filePath: string, transformOptions: TransformOptionsTsJest): string { const configs = this._configsFor(transformOptions) this._logger.debug({ fileName: filePath, transformOptions }, 'computing cache key for', filePath) diff --git a/src/types.ts b/src/types.ts index d83634f6f1..2dbaa54b9a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ -import type { Transformer } from '@jest/transform' +import type { Transformer, TransformOptions } from '@jest/transform' +import type { Config } from '@jest/types' import type * as _babel from 'babel__core' import type * as _ts from 'typescript' @@ -172,6 +173,16 @@ export interface TsJestConfig { stringifyContentPathRegex: string | undefined } +export interface TsJestProjectConfig extends Config.ProjectConfig { + globals: { + 'ts-jest': TsJestGlobalOptions + } +} + +export interface TransformOptionsTsJest extends TransformOptions { + config: TsJestProjectConfig +} + export type ResolvedModulesMap = Map | undefined /** * @internal