diff --git a/CHANGELOG.md b/CHANGELOG.md index 1530ba153658..7546cd3305eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[@jest/transformer]`: Support passing `supportsDynamicImport` and `supportsStaticESM` ([#9597](https://github.com/facebook/jest/pull/9597)) + ### Fixes ### Chore & Maintenance diff --git a/e2e/__tests__/__snapshots__/transform.test.ts.snap b/e2e/__tests__/__snapshots__/transform.test.ts.snap index 195359dad96c..be30ce803b52 100644 --- a/e2e/__tests__/__snapshots__/transform.test.ts.snap +++ b/e2e/__tests__/__snapshots__/transform.test.ts.snap @@ -6,7 +6,7 @@ FAIL __tests__/ignoredFile.test.js babel-jest: Babel ignores __tests__/ignoredFile.test.js - make sure to include the file in Jest's transformIgnorePatterns as well. - at loadBabelConfig (../../../packages/babel-jest/build/index.js:191:13) + at loadBabelConfig (../../../packages/babel-jest/build/index.js:212:13) `; exports[`babel-jest instruments only specific files and collects coverage 1`] = ` diff --git a/packages/babel-jest/package.json b/packages/babel-jest/package.json index 7b89bbf98e76..f4ba4805d801 100644 --- a/packages/babel-jest/package.json +++ b/packages/babel-jest/package.json @@ -20,7 +20,7 @@ "dependencies": { "@jest/transform": "^25.2.6", "@jest/types": "^25.2.6", - "@types/babel__core": "^7.1.0", + "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", "babel-preset-jest": "^25.2.6", "chalk": "^3.0.0", diff --git a/packages/babel-jest/src/__tests__/index.ts b/packages/babel-jest/src/__tests__/index.ts index d817c65f73ba..7bbc4f7c84d1 100644 --- a/packages/babel-jest/src/__tests__/index.ts +++ b/packages/babel-jest/src/__tests__/index.ts @@ -5,9 +5,18 @@ * LICENSE file in the root directory of this source tree. */ -import babelJest from '../index'; +import babelJest = require('../index'); +import {loadPartialConfig} from '../loadBabelConfig'; import {makeProjectConfig} from '../../../../TestUtils'; +jest.mock('../loadBabelConfig', () => { + const actual = jest.requireActual('@babel/core'); + + return { + loadPartialConfig: jest.fn((...args) => actual.loadPartialConfig(...args)), + }; +}); + //Mock data for all the tests const sourceString = ` const sum = (a, b) => a+b; @@ -21,7 +30,11 @@ const customMultiply = (obj, mul) => { customMultiply({a: 32, dummy: "test"}, 2); `; -test(`Returns source string with inline maps when no transformOptions is passed`, () => { +beforeEach(() => { + jest.clearAllMocks(); +}); + +test('Returns source string with inline maps when no transformOptions is passed', () => { const result = babelJest.process( sourceString, 'dummy_path.js', @@ -32,6 +45,55 @@ test(`Returns source string with inline maps when no transformOptions is passed` expect(result.map).toBeDefined(); expect(result.code).toMatch('//# sourceMappingURL'); expect(result.code).toMatch('customMultiply'); - expect(result.map.sources).toEqual(['dummy_path.js']); - expect(JSON.stringify(result.map.sourcesContent)).toMatch('customMultiply'); + expect(result.map!.sources).toEqual(['dummy_path.js']); + expect(JSON.stringify(result.map!.sourcesContent)).toMatch('customMultiply'); +}); + +describe('caller option correctly merges from defaults and options', () => { + test.each([ + [ + { + supportsDynamicImport: true, + supportsStaticESM: true, + }, + { + supportsDynamicImport: true, + supportsStaticESM: true, + }, + ], + [ + { + supportsDynamicImport: false, + supportsStaticESM: false, + }, + { + supportsDynamicImport: false, + supportsStaticESM: false, + }, + ], + [ + {supportsStaticESM: false}, + { + supportsDynamicImport: false, + supportsStaticESM: false, + }, + ], + [ + {supportsDynamicImport: true}, + { + supportsDynamicImport: true, + supportsStaticESM: false, + }, + ], + ])('%j -> %j', (input, output) => { + babelJest.process(sourceString, 'dummy_path.js', makeProjectConfig(), { + instrument: false, + ...input, + }); + + expect(loadPartialConfig).toHaveBeenCalledTimes(1); + expect(loadPartialConfig).toHaveBeenCalledWith( + expect.objectContaining({caller: {name: 'babel-jest', ...output}}), + ); + }); }); diff --git a/packages/babel-jest/src/index.ts b/packages/babel-jest/src/index.ts index 5d1cc9fa169b..c81fa2ad9fdd 100644 --- a/packages/babel-jest/src/index.ts +++ b/packages/babel-jest/src/index.ts @@ -14,8 +14,8 @@ import { PartialConfig, TransformOptions, transformSync as babelTransform, - loadPartialConfig, } from '@babel/core'; +import {loadPartialConfig} from './loadBabelConfig'; import chalk = require('chalk'); import slash = require('slash'); @@ -34,7 +34,9 @@ const createTransformer = ( options = { ...options, caller: { + ...options.caller, name: 'babel-jest', + supportsDynamicImport: false, supportsStaticESM: false, }, compact: false, @@ -46,9 +48,23 @@ const createTransformer = ( function loadBabelConfig( cwd: Config.Path, filename: Config.Path, + supportsDynamicImport?: boolean, + supportsStaticESM?: boolean, ): PartialConfig { // `cwd` first to allow incoming options to override it - const babelConfig = loadPartialConfig({cwd, ...options, filename}); + const babelConfig = loadPartialConfig({ + cwd, + ...options, + caller: { + ...options.caller, + name: 'babel-jest', + supportsDynamicImport: + supportsDynamicImport ?? options.caller!.supportsDynamicImport, + supportsStaticESM: + supportsStaticESM ?? options.caller!.supportsStaticESM, + }, + filename, + }); if (!babelConfig) { throw new Error( @@ -69,9 +85,14 @@ const createTransformer = ( fileData, filename, configString, - {config, instrument, rootDir}, + {config, instrument, rootDir, supportsDynamicImport, supportsStaticESM}, ) { - const babelOptions = loadBabelConfig(config.cwd, filename); + const babelOptions = loadBabelConfig( + config.cwd, + filename, + supportsDynamicImport, + supportsStaticESM, + ); const configPath = [ babelOptions.config || '', babelOptions.babelrc || '', @@ -98,7 +119,14 @@ const createTransformer = ( .digest('hex'); }, process(src, filename, config, transformOptions) { - const babelOptions = {...loadBabelConfig(config.cwd, filename).options}; + const babelOptions = { + ...loadBabelConfig( + config.cwd, + filename, + transformOptions?.supportsDynamicImport, + transformOptions?.supportsStaticESM, + ).options, + }; if (transformOptions && transformOptions.instrument) { babelOptions.auxiliaryCommentBefore = ' istanbul ignore next '; diff --git a/packages/babel-jest/src/loadBabelConfig.ts b/packages/babel-jest/src/loadBabelConfig.ts new file mode 100644 index 000000000000..91bbd1725980 --- /dev/null +++ b/packages/babel-jest/src/loadBabelConfig.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// this is a separate file so it can be mocked in tests +export {loadPartialConfig} from '@babel/core'; diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index cfef2ebf1704..374432fd8d3d 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -497,11 +497,9 @@ class Runtime { ): TransformationOptions { return { ...options, - changedFiles: this._coverageOptions.changedFiles, - collectCoverage: this._coverageOptions.collectCoverage, - collectCoverageFrom: this._coverageOptions.collectCoverageFrom, - collectCoverageOnlyFrom: this._coverageOptions.collectCoverageOnlyFrom, - coverageProvider: this._coverageOptions.coverageProvider, + ...this._coverageOptions, + supportsDynamicImport: false, + supportsStaticESM: false, }; } @@ -778,6 +776,8 @@ class Runtime { filename, this._getFullTransformationOptions(options), this._cacheFS[filename], + false, + false, ); // we only care about non-internal modules diff --git a/packages/jest-runtime/tsconfig.json b/packages/jest-runtime/tsconfig.json index 23c5279939a3..08abc7a9ddfc 100644 --- a/packages/jest-runtime/tsconfig.json +++ b/packages/jest-runtime/tsconfig.json @@ -17,6 +17,7 @@ {"path": "../jest-snapshot"}, {"path": "../jest-source-map"}, {"path": "../jest-test-result"}, + {"path": "../jest-transform"}, {"path": "../jest-types"}, {"path": "../jest-util"}, {"path": "../jest-validate"}, diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index d522d114c059..e7e7c33ef799 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -90,6 +90,8 @@ export default class ScriptTransformer { fileData: string, filename: Config.Path, instrument: boolean, + supportsDynamicImport: boolean, + supportsStaticESM: boolean, ): string { const configString = this._cache.configString; const transformer = this._getTransformer(filename); @@ -101,6 +103,8 @@ export default class ScriptTransformer { config: this._config, instrument, rootDir: this._config.rootDir, + supportsDynamicImport, + supportsStaticESM, }), ) .update(CACHE_VERSION) @@ -120,13 +124,21 @@ export default class ScriptTransformer { filename: Config.Path, content: string, instrument: boolean, + supportsDynamicImport: boolean, + supportsStaticESM: boolean, ): Config.Path { const baseCacheDir = HasteMap.getCacheFilePath( this._config.cacheDirectory, 'jest-transform-cache-' + this._config.name, VERSION, ); - const cacheKey = this._getCacheKey(content, filename, instrument); + const cacheKey = this._getCacheKey( + content, + filename, + instrument, + supportsDynamicImport, + supportsStaticESM, + ); // Create sub folders based on the cacheKey to avoid creating one // directory with many files. const cacheDir = path.join(baseCacheDir, cacheKey[0] + cacheKey[1]); @@ -191,13 +203,20 @@ export default class ScriptTransformer { return transform; } - private _instrumentFile(filename: Config.Path, content: string): string { + private _instrumentFile( + filename: Config.Path, + content: string, + supportsDynamicImport: boolean, + supportsStaticESM: boolean, + ): string { const result = babelTransform(content, { auxiliaryCommentBefore: ' istanbul ignore next ', babelrc: false, caller: { name: '@jest/transform', - supportsStaticESM: false, + // @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/42475 + supportsDynamicImport, + supportsStaticESM, }, configFile: false, filename, @@ -245,10 +264,18 @@ export default class ScriptTransformer { filepath: Config.Path, content: string, instrument: boolean, + supportsDynamicImport = false, + supportsStaticESM = false, ): TransformResult { const filename = this._getRealPath(filepath); const transform = this._getTransformer(filename); - const cacheFilePath = this._getFileCachePath(filename, content, instrument); + const cacheFilePath = this._getFileCachePath( + filename, + content, + instrument, + supportsDynamicImport, + supportsStaticESM, + ); let sourceMapPath: Config.Path | null = cacheFilePath + '.map'; // Ignore cache if `config.cache` is set (--no-cache) let code = this._config.cache ? readCodeCacheFile(cacheFilePath) : null; @@ -285,6 +312,8 @@ export default class ScriptTransformer { if (transform && shouldCallTransform) { const processed = transform.process(content, filename, this._config, { instrument, + supportsDynamicImport, + supportsStaticESM, }); if (typeof processed === 'string') { @@ -319,7 +348,12 @@ export default class ScriptTransformer { } if (!transformWillInstrument && instrument) { - code = this._instrumentFile(filename, transformed.code); + code = this._instrumentFile( + filename, + transformed.code, + supportsDynamicImport, + supportsStaticESM, + ); } else { code = transformed.code; } @@ -348,6 +382,8 @@ export default class ScriptTransformer { filename: Config.Path, options: Options | null, instrument: boolean, + supportsDynamicImport: boolean, + supportsStaticESM: boolean, fileSource?: string, ): TransformResult { const isInternalModule = !!(options && options.isInternalModule); @@ -371,6 +407,8 @@ export default class ScriptTransformer { filename, content, instrument, + supportsDynamicImport, + supportsStaticESM, ); code = transformedSource.code; @@ -393,6 +431,8 @@ export default class ScriptTransformer { filename: Config.Path, options: Options, fileSource?: string, + supportsDynamicImport = false, + supportsStaticESM = false, ): TransformResult { let scriptCacheKey = undefined; let instrument = false; @@ -412,6 +452,8 @@ export default class ScriptTransformer { filename, options, instrument, + supportsDynamicImport, + supportsStaticESM, fileSource, ); @@ -427,8 +469,12 @@ export default class ScriptTransformer { options: Options, fileSource: string, ): string { - const isInternalModule = options.isInternalModule; - const isCoreModule = options.isCoreModule; + const { + isCoreModule, + isInternalModule, + supportsDynamicImport, + supportsStaticESM, + } = options; const willTransform = !isInternalModule && !isCoreModule && this.shouldTransform(filename); @@ -437,6 +483,8 @@ export default class ScriptTransformer { filename, fileSource, false, + supportsDynamicImport, + supportsStaticESM, ); return transformedJsonSource; } @@ -465,7 +513,11 @@ export default class ScriptTransformer { (code, filename) => { try { transforming = true; - return this.transformSource(filename, code, false).code || code; + return ( + // we might wanna do `supportsDynamicImport` at some point + this.transformSource(filename, code, false, false, false).code || + code + ); } finally { transforming = false; } diff --git a/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap b/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap index 0200ad87482c..19c563f8e376 100644 --- a/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap +++ b/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap @@ -71,6 +71,8 @@ Object { }, "instrument": true, "rootDir": "/", + "supportsDynamicImport": false, + "supportsStaticESM": false, } `; diff --git a/packages/jest-transform/src/types.ts b/packages/jest-transform/src/types.ts index ff280e883afb..30075298bc26 100644 --- a/packages/jest-transform/src/types.ts +++ b/packages/jest-transform/src/types.ts @@ -22,6 +22,8 @@ export type Options = ShouldInstrumentOptions & Partial<{ isCoreModule: boolean; isInternalModule: boolean; + supportsDynamicImport: boolean; + supportsStaticESM: boolean; }>; // extends directly after https://github.com/sandersn/downlevel-dts/issues/33 is fixed @@ -38,15 +40,18 @@ export type TransformedSource = export type TransformResult = TransformTypes.TransformResult; -export type TransformOptions = { +export interface TransformOptions { instrument: boolean; -}; + // names are copied from babel + supportsDynamicImport?: boolean; + supportsStaticESM?: boolean; +} -export type CacheKeyOptions = { +// TODO: For Jest 26 we should combine these into one options shape +export interface CacheKeyOptions extends TransformOptions { config: Config.ProjectConfig; - instrument: boolean; rootDir: string; -}; +} export interface Transformer { canInstrument?: boolean; diff --git a/yarn.lock b/yarn.lock index 3137e4fe9b59..c026650daa0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2137,10 +2137,10 @@ resolved "https://registry.yarnpkg.com/@types/babel__code-frame/-/babel__code-frame-7.0.1.tgz#baf2529c4abbfb5e4008c845efcfe39a187e2f99" integrity sha512-FFfbQozKxYmOnCKFYV+EQprjBI7u2yaNc2ly/K9AhzyC8MzXtCtSRqptpw+HUJxhwCOo5mLwf1ATmzyhOaVbDg== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.0.4", "@types/babel__core@^7.1.0": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" - integrity sha512-tTnhWszAqvXnhW7m5jQU9PomXSiKXk2sFxpahXvI20SZKu9ylPi8WtIxueZ6ehDWikPT0jeFujMj3X4ZHuf3Tg== +"@types/babel__core@^7.0.0", "@types/babel__core@^7.0.4", "@types/babel__core@^7.1.0", "@types/babel__core@^7.1.7": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89" + integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0"