From 9bb0c054a3a7bd8949b0c12b2bb42348f69e2e2e Mon Sep 17 00:00:00 2001 From: Ahn Date: Tue, 12 Jan 2021 16:22:41 +0100 Subject: [PATCH] feat(compiler): support ESM for `isolatedModules: false` (#2269) Related to #1709 --- src/__helpers__/path.ts | 2 + src/__helpers__/processed-source.ts | 4 +- .../__snapshots__/ts-compiler.spec.ts.snap | 181 ++++-------------- src/compiler/ts-compiler.spec.ts | 99 ++++------ src/compiler/ts-compiler.ts | 61 +++--- src/transformers/hoist-jest.spec.ts | 6 +- website/docs/esm-support.md | 37 +++- website/docs/options/useESM.md | 4 +- 8 files changed, 153 insertions(+), 241 deletions(-) diff --git a/src/__helpers__/path.ts b/src/__helpers__/path.ts index 4ac904fed0..ef1ce5b7a8 100644 --- a/src/__helpers__/path.ts +++ b/src/__helpers__/path.ts @@ -11,3 +11,5 @@ export function tempDir(ns: string): string { return dir } + +export const mockFolder = join(process.cwd(), 'src', '__mocks__') diff --git a/src/__helpers__/processed-source.ts b/src/__helpers__/processed-source.ts index af57ea4557..0a2e82f24b 100644 --- a/src/__helpers__/processed-source.ts +++ b/src/__helpers__/processed-source.ts @@ -2,6 +2,8 @@ import { isAbsolute, relative } from 'path' import type { RawSourceMap } from 'source-map' +import { SOURCE_MAPPING_PREFIX } from '../compiler/compiler-utils' + import { ROOT } from './path' import { ParsedSourceWithMaps, parseSource, relativisePaths, rewriteSourceMaps } from './source-maps' @@ -19,7 +21,7 @@ export default class ProcessedSource { return parseSource(this.output) } get outputCodeWithoutMaps(): string { - return this.parsedSource.source + return this.output.substring(0, this.output.indexOf(SOURCE_MAPPING_PREFIX)) } get outputSourceMaps(): RawSourceMap | undefined { return this.parsedSource.sourceMaps diff --git a/src/compiler/__snapshots__/ts-compiler.spec.ts.snap b/src/compiler/__snapshots__/ts-compiler.spec.ts.snap index 972e486d86..83c23bd306 100644 --- a/src/compiler/__snapshots__/ts-compiler.spec.ts.snap +++ b/src/compiler/__snapshots__/ts-compiler.spec.ts.snap @@ -1,39 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TsCompiler isolatedModule false allowJs option should compile js file for allowJs true with outDir 1`] = ` - ===[ FILE: test-allow-js.js ]=================================================== - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.default = 42; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1hbGxvdy1qcy5qcyIsIm1hcHBpbmdzIjoiOztBQUFBLGtCQUFlLEVBQUUsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWFsbG93LWpzLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IDQyIl0sInZlcnNpb24iOjN9 - ===[ INLINE SOURCE MAPS ]======================================================= - file: test-allow-js.js - mappings: ';;AAAA,kBAAe,EAAE,CAAA' - names: [] - sources: - - test-allow-js.js - sourcesContent: - - export default 42 - version: 3 - ================================================================================ +"\\"use strict\\"; +Object.defineProperty(exports, \\"__esModule\\", { value: true }); +exports.default = 42; +//# " `; exports[`TsCompiler isolatedModule false allowJs option should compile js file for allowJs true without outDir 1`] = ` - ===[ FILE: test-allow-js.js ]=================================================== - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.default = 42; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1hbGxvdy1qcy5qcyIsIm1hcHBpbmdzIjoiOztBQUFBLGtCQUFlLEVBQUUsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWFsbG93LWpzLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IDQyIl0sInZlcnNpb24iOjN9 - ===[ INLINE SOURCE MAPS ]======================================================= - file: test-allow-js.js - mappings: ';;AAAA,kBAAe,EAAE,CAAA' - names: [] - sources: - - test-allow-js.js - sourcesContent: - - export default 42 - version: 3 - ================================================================================ +"\\"use strict\\"; +Object.defineProperty(exports, \\"__esModule\\", { value: true }); +exports.default = 42; +//# " `; exports[`TsCompiler isolatedModule false diagnostics should throw error when cannot compile 1`] = ` @@ -55,51 +33,24 @@ Array [ `; exports[`TsCompiler isolatedModule false jsx option should compile tsx file for jsx preserve 1`] = ` - ===[ FILE: test-jsx.tsx ]======================================================= - "use strict"; - const App = () => { - return <>Test; - }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gudHN4IiwibWFwcGluZ3MiOiI7QUFDUSxNQUFNLEdBQUcsR0FBRyxHQUFHLEVBQUU7SUFDZixPQUFPLEVBQUUsSUFBSSxHQUFHLENBQUE7QUFDbEIsQ0FBQyxDQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbInRlc3QtanN4LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgICAgfVxuICAgICAgIl0sInZlcnNpb24iOjN9 - ===[ INLINE SOURCE MAPS ]======================================================= - file: test-jsx.tsx - mappings: ';AACQ,MAAM,GAAG,GAAG,GAAG,EAAE;IACf,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' - names: [] - sources: - - test-jsx.tsx - sourcesContent: - - |2- - - const App = () => { - return <>Test - } - - version: 3 - ================================================================================ +"\\"use strict\\"; +const App = () => { + return <>Test; +}; +//# " `; exports[`TsCompiler isolatedModule false jsx option should compile tsx file for other jsx options 1`] = ` - ===[ FILE: test-jsx.tsx ]======================================================= - "use strict"; - const App = () => { - return React.createElement(React.Fragment, null, "Test"); - }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gudHN4IiwibWFwcGluZ3MiOiI7QUFDUSxNQUFNLEdBQUcsR0FBRyxHQUFHLEVBQUU7SUFDZixPQUFPLGlEQUFTLENBQUE7QUFDbEIsQ0FBQyxDQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbInRlc3QtanN4LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgICAgfVxuICAgICAgIl0sInZlcnNpb24iOjN9 - ===[ INLINE SOURCE MAPS ]======================================================= - file: test-jsx.tsx - mappings: ';AACQ,MAAM,GAAG,GAAG,GAAG,EAAE;IACf,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA' - names: [] - sources: - - test-jsx.tsx - sourcesContent: - - |2- - - const App = () => { - return <>Test - } - - version: 3 - ================================================================================ +"\\"use strict\\"; +const App = () => { + return React.createElement(React.Fragment, null, \\"Test\\"); +}; +//# " +`; + +exports[`TsCompiler isolatedModule false should compile codes with useESM true 1`] = ` +"export const thing = { a: 1, b: 2 }; +//# " `; exports[`TsCompiler isolatedModule true diagnostics should report diagnostics related to codes with pathRegex config is undefined 1`] = `"foo.ts(2,23): error TS1005: '=>' expected."`; @@ -107,79 +58,29 @@ exports[`TsCompiler isolatedModule true diagnostics should report diagnostics re exports[`TsCompiler isolatedModule true diagnostics should report diagnostics related to codes with pathRegex config matches file name 1`] = `"foo.ts(2,23): error TS1005: '=>' expected."`; exports[`TsCompiler isolatedModule true jsx option should compile tsx file for jsx preserve 1`] = ` - ===[ FILE: foo.tsx ]============================================================ - "use strict"; - const App = () => { - return <>Test; - }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiO0FBQ1EsTUFBTSxHQUFHLEdBQUcsR0FBRyxFQUFFO0lBQ2YsT0FBTyxFQUFFLElBQUksR0FBRyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJmb28udHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgICB9XG4gICAgICAiXSwidmVyc2lvbiI6M30= - ===[ INLINE SOURCE MAPS ]======================================================= - file: foo.tsx - mappings: ';AACQ,MAAM,GAAG,GAAG,GAAG,EAAE;IACf,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' - names: [] - sources: - - foo.tsx - sourcesContent: - - |2- - - const App = () => { - return <>Test - } - - version: 3 - ================================================================================ +"\\"use strict\\"; +const App = () => { + return <>Test; +}; +//# " `; exports[`TsCompiler isolatedModule true jsx option should compile tsx file for other jsx options 1`] = ` - ===[ FILE: foo.tsx ]============================================================ - "use strict"; - const App = () => { - return React.createElement(React.Fragment, null, "Test"); - }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiO0FBQ1EsTUFBTSxHQUFHLEdBQUcsR0FBRyxFQUFFO0lBQ2YsT0FBTyxpREFBUyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJmb28udHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgICB9XG4gICAgICAiXSwidmVyc2lvbiI6M30= - ===[ INLINE SOURCE MAPS ]======================================================= - file: foo.tsx - mappings: ';AACQ,MAAM,GAAG,GAAG,GAAG,EAAE;IACf,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA' - names: [] - sources: - - foo.tsx - sourcesContent: - - |2- - - const App = () => { - return <>Test - } - - version: 3 - ================================================================================ +"\\"use strict\\"; +const App = () => { + return React.createElement(React.Fragment, null, \\"Test\\"); +}; +//# " `; exports[`TsCompiler isolatedModule true should compile js file for allowJs true 1`] = ` - ===[ FILE: foo.js ]============================================================= - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.default = 42; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLmpzIiwibWFwcGluZ3MiOiI7O0FBQUEsa0JBQWUsRUFBRSxDQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbImZvby5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCA0MiJdLCJ2ZXJzaW9uIjozfQ== - ===[ INLINE SOURCE MAPS ]======================================================= - file: foo.js - mappings: ';;AAAA,kBAAe,EAAE,CAAA' - names: [] - sources: - - foo.js - sourcesContent: - - export default 42 - version: 3 - ================================================================================ +"\\"use strict\\"; +Object.defineProperty(exports, \\"__esModule\\", { value: true }); +exports.default = 42; +//# " `; -exports[`TsCompiler isolatedModule true support ESM should transpile codes to correct syntax with supportsStaticESM and useESM options 1`] = `99`; - -exports[`TsCompiler isolatedModule true support ESM should transpile codes to correct syntax with supportsStaticESM and useESM options 2`] = `99`; - -exports[`TsCompiler isolatedModule true support ESM should transpile codes to correct syntax with supportsStaticESM and useESM options 3`] = `99`; - -exports[`TsCompiler isolatedModule true support ESM should transpile codes to correct syntax with supportsStaticESM and useESM options 4`] = `1`; - -exports[`TsCompiler isolatedModule true support ESM should transpile codes to correct syntax with supportsStaticESM and useESM options 5`] = `1`; - -exports[`TsCompiler isolatedModule true support ESM should transpile codes to correct syntax with supportsStaticESM and useESM options 6`] = `1`; +exports[`TsCompiler isolatedModule true should transpile code with useESM true 1`] = ` +"export const thing = { a: 1, b: 2 }; +//# " +`; diff --git a/src/compiler/ts-compiler.spec.ts b/src/compiler/ts-compiler.spec.ts index 3c4d5d4c6e..42fc3044fb 100644 --- a/src/compiler/ts-compiler.spec.ts +++ b/src/compiler/ts-compiler.spec.ts @@ -2,10 +2,10 @@ import { readFileSync } from 'fs' import { join } from 'path' import { LogLevels } from 'bs-logger' -import ts from 'typescript' import { makeCompiler } from '../__helpers__/fakers' import { logTargetMock } from '../__helpers__/mocks' +import { mockFolder } from '../__helpers__/path' import ProcessedSource from '../__helpers__/processed-source' import { TS_JEST_OUT_DIR } from '../config/config-set' @@ -17,6 +17,17 @@ describe('TsCompiler', () => { isolatedModules: true, } + test('should transpile code with useESM true', () => { + const compiler = makeCompiler({ + tsJestConfig: { ...baseTsJestConfig, useESM: true }, + }) + const fileName = join(mockFolder, 'thing.spec.ts') + + const compiledOutput = compiler.getCompiledOutput(readFileSync(fileName, 'utf-8'), fileName, true) + + expect(new ProcessedSource(compiledOutput, fileName).outputCodeWithoutMaps).toMatchSnapshot() + }) + it('should compile js file for allowJs true', () => { const fileName = 'foo.js' const compiler = makeCompiler({ @@ -24,9 +35,9 @@ describe('TsCompiler', () => { }) const source = 'export default 42' - const compiled = compiler.getCompiledOutput(source, fileName, false) + const compiledOutput = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + expect(new ProcessedSource(compiledOutput, fileName).outputCodeWithoutMaps).toMatchSnapshot() }) describe('jsx option', () => { @@ -46,9 +57,9 @@ describe('TsCompiler', () => { }, }, }) - const compiled = compiler.getCompiledOutput(source, fileName, false) + const compiledOutput = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + expect(new ProcessedSource(compiledOutput, fileName).outputCodeWithoutMaps).toMatchSnapshot() }) it('should compile tsx file for other jsx options', () => { @@ -60,9 +71,9 @@ describe('TsCompiler', () => { }, }, }) - const compiled = compiler.getCompiledOutput(source, fileName, false) + const compiledOutput = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + expect(new ProcessedSource(compiledOutput, fileName).outputCodeWithoutMaps).toMatchSnapshot() }) }) @@ -72,9 +83,9 @@ describe('TsCompiler', () => { it('should have correct source maps without mapRoot', () => { const compiler = makeCompiler({ tsJestConfig: { ...baseTsJestConfig, tsconfig: false } }) - const compiled = compiler.getCompiledOutput(source, fileName, false) + const compiledOutput = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName).outputSourceMaps).toMatchObject({ + expect(new ProcessedSource(compiledOutput, fileName).outputSourceMaps).toMatchObject({ file: fileName, sources: [fileName], sourcesContent: [source], @@ -166,57 +177,6 @@ const t: string = f(5) ).not.toThrowError() }) }) - - describe('support ESM', () => { - test.each([ - { - supportsStaticESM: true, - useESM: true, - moduleKind: 'esnext', - }, - { - supportsStaticESM: true, - useESM: true, - moduleKind: 'amd', - }, - { - supportsStaticESM: true, - useESM: true, - moduleKind: undefined, - }, - { - supportsStaticESM: false, - useESM: true, - moduleKind: 'esnext', - }, - { - supportsStaticESM: true, - useESM: false, - moduleKind: 'amd', - }, - { - supportsStaticESM: false, - useESM: false, - moduleKind: 'es2015', - }, - ])('should transpile codes to correct syntax with supportsStaticESM and useESM options', (data) => { - const transpileModuleSpy = (ts.transpileModule = jest.fn().mockReturnValueOnce({ - outputText: 'var foo = 1', - diagnostics: [], - sourceMapText: '{}', - })) - const fileContent = `const foo = import('./foo')` - const fileName = 'foo.ts' - - const compiler = makeCompiler({ - tsJestConfig: { ...baseTsJestConfig, tsconfig: { module: data.moduleKind as any }, useESM: data.useESM }, - }) - compiler.getCompiledOutput(fileContent, fileName, data.supportsStaticESM) - - expect(transpileModuleSpy).toHaveBeenCalled() - expect(transpileModuleSpy.mock.calls[0][1].compilerOptions.module).toMatchSnapshot() - }) - }) }) describe('isolatedModule false', () => { @@ -227,6 +187,17 @@ const t: string = f(5) logTarget.clear() }) + test('should compile codes with useESM true', () => { + const compiler = makeCompiler({ + tsJestConfig: { ...baseTsJestConfig, useESM: true }, + }) + const fileName = join(mockFolder, 'thing.spec.ts') + + const compiledOutput = compiler.getCompiledOutput(readFileSync(fileName, 'utf-8'), fileName, true) + + expect(new ProcessedSource(compiledOutput, fileName).outputCodeWithoutMaps).toMatchSnapshot() + }) + describe('allowJs option', () => { const fileName = 'test-allow-js.js' const source = 'export default 42' @@ -242,7 +213,7 @@ const t: string = f(5) const compiled = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + expect(new ProcessedSource(compiled, fileName).outputCodeWithoutMaps).toMatchSnapshot() }) it('should compile js file for allowJs true without outDir', () => { @@ -254,7 +225,7 @@ const t: string = f(5) ) const compiled = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + expect(new ProcessedSource(compiled, fileName).outputCodeWithoutMaps).toMatchSnapshot() }) }) @@ -281,7 +252,7 @@ const t: string = f(5) const compiled = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + expect(new ProcessedSource(compiled, fileName).outputCodeWithoutMaps).toMatchSnapshot() }) it('should compile tsx file for other jsx options', () => { @@ -297,7 +268,7 @@ const t: string = f(5) ) const compiled = compiler.getCompiledOutput(source, fileName, false) - expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + expect(new ProcessedSource(compiled, fileName).outputCodeWithoutMaps).toMatchSnapshot() }) }) diff --git a/src/compiler/ts-compiler.ts b/src/compiler/ts-compiler.ts index 85e8c6b258..5fb1475fb0 100644 --- a/src/compiler/ts-compiler.ts +++ b/src/compiler/ts-compiler.ts @@ -2,14 +2,14 @@ import { basename, normalize, relative } from 'path' import { LogContexts, Logger, LogLevels } from 'bs-logger' import memoize from 'lodash/memoize' -import { +import type { EmitOutput, LanguageService, LanguageServiceHost, ParsedCommandLine, ResolvedModuleFull, TranspileOutput, - ModuleKind, + CompilerOptions, } from 'typescript' import type { ConfigSet } from '../config/config-set' @@ -20,8 +20,6 @@ import { Errors, interpolate } from '../utils/messages' import { updateOutput } from './compiler-utils' -const AVAILABLE_ESM_MODULE_KINDS = [ModuleKind.ES2015, ModuleKind.ES2020, ModuleKind.ESNext] - /** * @internal */ @@ -31,6 +29,7 @@ export class TsCompiler implements CompilerInstance { private readonly _parsedTsConfig: ParsedCommandLine private readonly _compilerCacheFS: Map = new Map() private readonly _jestCacheFS: StringMap + private _compilerOptions: CompilerOptions private _cachedReadFile: ((fileName: string) => string | undefined) | undefined private _projectVersion = 1 private _languageService: LanguageService | undefined @@ -40,16 +39,13 @@ export class TsCompiler implements CompilerInstance { this._logger = rootLogger.child({ namespace: 'ts-compiler' }) this._parsedTsConfig = this.configSet.parsedTsConfig as ParsedCommandLine this._jestCacheFS = jestCacheFS + this._compilerOptions = { ...this._parsedTsConfig.options, module: this._ts.ModuleKind.CommonJS } if (!this.configSet.isolatedModules) { this._createLanguageService() } } private _createLanguageService(): void { - const compilerOptions = { - ...this._parsedTsConfig.options, - module: ModuleKind.CommonJS, - } const serviceHostTraceCtx = { namespace: 'ts:serviceHost', call: null, @@ -69,7 +65,11 @@ export class TsCompiler implements CompilerInstance { realpath: this._ts.sys.realpath && memoize(this._ts.sys.realpath), getDirectories: memoize(this._ts.sys.getDirectories), } - const moduleResolutionCache = this._ts.createModuleResolutionCache(this.configSet.cwd, (x) => x, compilerOptions) + const moduleResolutionCache = this._ts.createModuleResolutionCache( + this.configSet.cwd, + (x) => x, + this._compilerOptions, + ) /* istanbul ignore next */ const serviceHost: LanguageServiceHost = { getProjectVersion: () => String(this._projectVersion), @@ -114,15 +114,15 @@ export class TsCompiler implements CompilerInstance { realpath: this._ts.sys.realpath && memoize(this._ts.sys.realpath), getNewLine: () => LINE_FEED, getCurrentDirectory: () => this.configSet.cwd, - getCompilationSettings: () => compilerOptions, - getDefaultLibFileName: () => this._ts.getDefaultLibFilePath(compilerOptions), + getCompilationSettings: () => this._compilerOptions, + getDefaultLibFileName: () => this._ts.getDefaultLibFilePath(this._compilerOptions), getCustomTransformers: () => this.configSet.customTransformers, resolveModuleNames: (moduleNames: string[], containingFile: string): (ResolvedModuleFull | undefined)[] => moduleNames.map((moduleName) => { const { resolvedModule } = this._ts.resolveModuleName( moduleName, containingFile, - compilerOptions, + this._compilerOptions, moduleResolutionHost, moduleResolutionCache, ) @@ -144,6 +144,28 @@ export class TsCompiler implements CompilerInstance { } getCompiledOutput(fileContent: string, fileName: string, supportsStaticESM: boolean): string { + let moduleKind = this._compilerOptions.module + let esModuleInterop = this._compilerOptions.esModuleInterop + let allowSyntheticDefaultImports = this._compilerOptions.allowSyntheticDefaultImports + if (supportsStaticESM && this.configSet.useESM) { + moduleKind = + !moduleKind || + (moduleKind && + ![this._ts.ModuleKind.ES2015, this._ts.ModuleKind.ES2020, this._ts.ModuleKind.ESNext].includes(moduleKind)) + ? this._ts.ModuleKind.ESNext + : moduleKind + // Make sure `esModuleInterop` and `allowSyntheticDefaultImports` true to support import CJS into ESM + esModuleInterop = true + allowSyntheticDefaultImports = true + } else { + moduleKind = this._ts.ModuleKind.CommonJS + } + this._compilerOptions = { + ...this._compilerOptions, + allowSyntheticDefaultImports, + esModuleInterop, + module: moduleKind, + } if (this._languageService) { this._logger.debug({ fileName }, 'getCompiledOutput(): compiling using language service') @@ -169,25 +191,12 @@ export class TsCompiler implements CompilerInstance { return updateOutput(output.outputFiles[1].text, fileName, output.outputFiles[0].text) } else { - let moduleKind = this._parsedTsConfig.options.module - if (supportsStaticESM && this.configSet.useESM) { - moduleKind = - !moduleKind || (moduleKind && !AVAILABLE_ESM_MODULE_KINDS.includes(moduleKind)) - ? ModuleKind.ESNext - : moduleKind - } else { - moduleKind = ModuleKind.CommonJS - } - this._logger.debug({ fileName }, 'getCompiledOutput(): compiling as isolated module') const result: TranspileOutput = this._ts.transpileModule(fileContent, { fileName, transformers: this.configSet.customTransformers, - compilerOptions: { - ...this._parsedTsConfig.options, - module: moduleKind, - }, + compilerOptions: this._compilerOptions, reportDiagnostics: this.configSet.shouldReportDiagnostics(fileName), }) if (result.diagnostics && this.configSet.shouldReportDiagnostics(fileName)) { diff --git a/src/transformers/hoist-jest.spec.ts b/src/transformers/hoist-jest.spec.ts index 513ec8e8a5..6dbacb0add 100644 --- a/src/transformers/hoist-jest.spec.ts +++ b/src/transformers/hoist-jest.spec.ts @@ -1,5 +1,5 @@ import { testing } from 'bs-logger' -import * as tsc from 'typescript' +import ts from 'typescript' import * as hoist from './hoist-jest' @@ -116,8 +116,8 @@ const CODE_WITH_HOISTING_HAS_JEST_GLOBALS = ` ` const logger = testing.createLoggerMock() -const createFactory = () => hoist.factory({ logger, compilerModule: tsc } as any) -const transpile = (source: string) => tsc.transpileModule(source, { transformers: { before: [createFactory()] } }) +const createFactory = () => hoist.factory({ logger, compilerModule: ts } as any) +const transpile = (source: string) => ts.transpileModule(source, { transformers: { before: [createFactory()] } }) describe('hoisting', () => { it('should have correct signature', () => { diff --git a/website/docs/esm-support.md b/website/docs/esm-support.md index 15646234f6..1fb562aa24 100644 --- a/website/docs/esm-support.md +++ b/website/docs/esm-support.md @@ -4,7 +4,36 @@ title: ESM Support To use `ts-jest` with ESM support, you'll first need to check [ESM Jest documentation](https://jestjs.io/docs/en/ecmascript-modules). -`ts-jest` supports ESM via a config option `useESM`. When this option is enabled and depending on which files Jest supports -ESM, `ts-jest` will transform codes to ESM syntax. There are also 3 presets to use with `useESM` option. - -More information see [useESM option](options/useESM.md) and [ESM presets](presets.md) +`ts-jest` supports ESM via a config option [useESM](options/useESM.md) in combination with jest config option [extensionsToTreatAsEsm](https://jestjs.io/docs/en/next/configuration#extensionstotreatasesm-arraystring). + +There are also [3 presets](presets.md) to work with ESM. + +### Examples + +```js +// jest.config.js +module.exports = { + // [...] + "extensionsToTreatAsEsm": ['.ts'], + globals: { + 'ts-jest': { + useESM: true, + }, + }, +} +``` + +```json5 +// OR package.json +{ + // [...] + "jest": { + "extensionsToTreatAsEsm": [".ts"], + "globals": { + "ts-jest": { + "useESM": true, + } + } + } +} +``` diff --git a/website/docs/options/useESM.md b/website/docs/options/useESM.md index e1fe4bf54a..8c68e1dfb7 100644 --- a/website/docs/options/useESM.md +++ b/website/docs/options/useESM.md @@ -4,9 +4,7 @@ title: useESM option The `useESM` option allows `ts-jest` to transform codes to ESM syntax **if possible**. -The default value is **false**, `ts-jest` will transform codes to `CommonJS` syntax. - -Currently `ts-jest` only supports this option in combination with `isolatedModule: true`. +The default value is **false**, `ts-jest` will transform codes to `CommonJS` syntax. ### Examples