diff --git a/e2e/jest.config.js b/e2e/jest.config.js index b572354ac8..61581ddcac 100644 --- a/e2e/jest.config.js +++ b/e2e/jest.config.js @@ -10,6 +10,7 @@ module.exports = { '/jest-globals', '/path-mapping', '/snapshot-serializers', + '/transform-mjs', '/with-babel', ], }; diff --git a/e2e/path-mapping/__tests__/path-mapping.ts b/e2e/path-mapping/__tests__/path-mapping.ts index 89202a8fbe..e08995a145 100644 --- a/e2e/path-mapping/__tests__/path-mapping.ts +++ b/e2e/path-mapping/__tests__/path-mapping.ts @@ -2,6 +2,7 @@ import { bar } from '@bar/bar-constant'; test('path mapping should work without being affected by resolver', async () => { expect(bar).toBe(1); + // @ts-expect-error testing purpose await expect(import('@foo/foo-constant')).rejects.toMatchInlineSnapshot( `[Error: Cannot find module '@foo/foo-constant' from '__tests__/path-mapping.ts']`, ); diff --git a/e2e/transform-mjs/__tests__/transform-mjs.ts b/e2e/transform-mjs/__tests__/transform-mjs.ts new file mode 100644 index 0000000000..f38748e72d --- /dev/null +++ b/e2e/transform-mjs/__tests__/transform-mjs.ts @@ -0,0 +1,6 @@ +// @ts-expect-error TypeScript < 4.5 doesn't support `.mjs` import +import * as foo from '../foo.mjs'; + +test('should transform mjs files', async () => { + expect(foo.pi).toBeDefined(); +}); diff --git a/e2e/transform-mjs/foo.mjs b/e2e/transform-mjs/foo.mjs new file mode 100644 index 0000000000..c24004f1b7 --- /dev/null +++ b/e2e/transform-mjs/foo.mjs @@ -0,0 +1,3 @@ +const pi = 3.14 + +export { pi } \ No newline at end of file diff --git a/e2e/transform-mjs/jest.config.js b/e2e/transform-mjs/jest.config.js new file mode 100644 index 0000000000..53fefd344f --- /dev/null +++ b/e2e/transform-mjs/jest.config.js @@ -0,0 +1,12 @@ +/** @type {import('ts-jest/dist/types').ProjectConfigTsJest} */ +module.exports = { + displayName: 'transform-mjs', + globals: { + 'ts-jest': { + tsconfig: '/../tsconfig.json', + }, + }, + transform: { + '^.+\\.(ts|js|mjs|html)$': '/../../build/index.js', + }, +}; diff --git a/package.json b/package.json index 4c9b9e9534..4cc32309cc 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "update-e2e": "node scripts/update-e2e-deps.js" }, "dependencies": { + "esbuild": "0.13.12", "jest-environment-jsdom": "^27.0.0", "pretty-format": "^27.0.0", "ts-jest": "^27.0.0" diff --git a/src/__tests__/__snapshots__/ng-jest-transformer.spec.ts.snap b/src/__tests__/__snapshots__/ng-jest-transformer.spec.ts.snap new file mode 100644 index 0000000000..93ff237e26 --- /dev/null +++ b/src/__tests__/__snapshots__/ng-jest-transformer.spec.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NgJestTransformer should process successfully a mjs file to CommonJS codes 1`] = ` +Array [ + " + const pi = parseFloat(3.124); + + export { pi }; + ", + Object { + "format": "cjs", + "loader": "js", + "sourcemap": false, + "target": "es2015", + }, +] +`; + +exports[`NgJestTransformer should process successfully a mjs file to CommonJS codes 2`] = ` +Array [ + " + const pi = parseFloat(3.124); + + export { pi }; + ", + Object { + "format": "cjs", + "loader": "js", + "sourcemap": true, + "target": "es2016", + }, +] +`; + +exports[`NgJestTransformer should process successfully a mjs file to CommonJS codes 3`] = ` +Array [ + " + const pi = parseFloat(3.124); + + export { pi }; + ", + Object { + "format": "cjs", + "loader": "js", + "sourcemap": true, + "target": "es2015", + }, +] +`; diff --git a/src/__tests__/ng-jest-transformer.spec.ts b/src/__tests__/ng-jest-transformer.spec.ts index 3bdc5800d5..d71217f471 100644 --- a/src/__tests__/ng-jest-transformer.spec.ts +++ b/src/__tests__/ng-jest-transformer.spec.ts @@ -1,9 +1,20 @@ +import { transformSync } from 'esbuild'; + import { NgJestCompiler } from '../compiler/ng-jest-compiler'; import { NgJestConfig } from '../config/ng-jest-config'; import { NgJestTransformer } from '../ng-jest-transformer'; const tr = new NgJestTransformer(); +jest.mock('esbuild', () => { + return { + transformSync: jest.fn().mockReturnValue({ + code: 'bla bla', + map: JSON.stringify({ version: 1, sourceContent: 'foo foo' }), + }), + }; +}); + describe('NgJestTransformer', () => { test('should create NgJestCompiler and NgJestConfig instances', () => { // @ts-expect-error testing purpose @@ -22,43 +33,26 @@ describe('NgJestTransformer', () => { expect(cs).toBeInstanceOf(NgJestConfig); }); - test('should process successfully a mjs file with CommonJS mode', () => { + test.each([ + { + tsconfig: { + sourceMap: false, + }, + }, + { + tsconfig: { + target: 'es2016', + }, + }, + { + tsconfig: {}, + }, + ])('should process successfully a mjs file to CommonJS codes', ({ tsconfig }) => { const result = tr.process( ` const pi = parseFloat(3.124); - export default pi; - `, - 'foo.mjs', - { - config: { - cwd: process.cwd(), - extensionsToTreatAsEsm: [], - testMatch: [], - testRegex: [], - }, - } as any, // eslint-disable-line @typescript-eslint/no-explicit-any - ); - - expect(typeof result).toBe('object'); - // @ts-expect-error `code` is a property of `TransformSource` - expect(result.code).toMatchInlineSnapshot(` - "\\"use strict\\"; - Object.defineProperty(exports, \\"__esModule\\", { value: true }); - const pi = parseFloat(3.124); - exports.default = pi; - //# sourceMappingURL=foo.mjs.js.map" - `); - // @ts-expect-error `map` is a property of `TransformSource` - expect(result.map).toBeDefined(); - }); - - test('should process successfully a mjs file with ESM mode', () => { - const result = tr.process( - ` - const pi = parseFloat(3.124); - - export default pi; + export { pi }; `, 'foo.mjs', { @@ -69,50 +63,20 @@ describe('NgJestTransformer', () => { testRegex: [], globals: { 'ts-jest': { - useESM: true, + tsconfig, }, }, }, - supportsStaticESM: true, } as any, // eslint-disable-line @typescript-eslint/no-explicit-any ); - expect(typeof result).toBe('object'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((transformSync as unknown as jest.MockInstance).mock.calls[0]).toMatchSnapshot(); + // @ts-expect-error `code` is a property of `TransformSource` + expect(result.code).toBeDefined(); // @ts-expect-error `code` is a property of `TransformSource` - expect(result.code).toMatchInlineSnapshot(` - "const pi = parseFloat(3.124); - export default pi; - //# sourceMappingURL=foo.mjs.js.map" - `); - // @ts-expect-error `map` is a property of `TransformSource` expect(result.map).toBeDefined(); - }); - - test('should throw syntax error for mjs file with checkJs true', () => { - expect(() => - tr.process( - ` - const pi == parseFloat(3.124); - - export default pi; - `, - 'foo.mjs', - { - config: { - cwd: process.cwd(), - extensionsToTreatAsEsm: [], - testMatch: [], - testRegex: [], - globals: { - 'ts-jest': { - tsconfig: { - checkJs: true, - }, - }, - }, - }, - } as any, // eslint-disable-line @typescript-eslint/no-explicit-any - ), - ).toThrowError(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (transformSync as unknown as jest.MockInstance).mockClear(); }); }); diff --git a/src/ng-jest-transformer.ts b/src/ng-jest-transformer.ts index c3b57f3d1e..60925e45e5 100644 --- a/src/ng-jest-transformer.ts +++ b/src/ng-jest-transformer.ts @@ -2,6 +2,7 @@ import path from 'path'; import type { TransformedSource } from '@jest/transform'; import type { Config } from '@jest/types'; +import { transformSync } from 'esbuild'; import { ConfigSet } from 'ts-jest/dist/config/config-set'; import { TsJestTransformer } from 'ts-jest/dist/ts-jest-transformer'; import type { ProjectConfigTsJest, TransformOptionsTsJest } from 'ts-jest/dist/types'; @@ -26,33 +27,19 @@ export class NgJestTransformer extends TsJestTransformer { const configSet = this._createConfigSet(transformOptions.config); /** * TypeScript < 4.5 doesn't support compiling `.mjs` file by default when running `tsc` which throws error - * ``` - * File '/Users/ahn/ts-jest-only-example/foo.mjs' has an unsupported extension. The only supported extensions are - * '.ts', '.tsx', '.d.ts', '.js', '.jsx'. - * ``` - * However, `transpileModule` API supports `.mjs` so we use it as a workaround. */ if (path.extname(filePath) === '.mjs') { - const compilerOptions = configSet.parsedTsConfig.options; - const compilerModule = configSet.compilerModule; - const { outputText, sourceMapText, diagnostics } = compilerModule.transpileModule(fileContent, { - compilerOptions: { - ...compilerOptions, - module: - transformOptions.supportsStaticESM && configSet.useESM - ? compilerModule.ModuleKind.ES2020 - : compilerModule.ModuleKind.CommonJS, - }, - fileName: filePath, - reportDiagnostics: configSet.shouldReportDiagnostics(filePath), + const { target, sourceMap } = configSet.parsedTsConfig.options; + const { code, map } = transformSync(fileContent, { + loader: 'js', + format: 'cjs', + target: target === configSet.compilerModule.ScriptTarget.ES2015 ? 'es2015' : 'es2016', + sourcemap: sourceMap, }); - if (diagnostics?.length) { - configSet.raiseDiagnostics(diagnostics, filePath); - } return { - code: outputText, - map: sourceMapText, + code, + map, }; } else { return super.process(fileContent, filePath, transformOptions); diff --git a/yarn.lock b/yarn.lock index d941b7abdc..e307dff672 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2623,6 +2623,114 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild-android-arm64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.12.tgz#e1f199dc05405cdc6670c00fb6c793822bf8ae4c" + integrity sha512-TSVZVrb4EIXz6KaYjXfTzPyyRpXV5zgYIADXtQsIenjZ78myvDGaPi11o4ZSaHIwFHsuwkB6ne5SZRBwAQ7maw== + +esbuild-darwin-64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.12.tgz#f5c59e622955c01f050e5a7ac9c1d41db714b94d" + integrity sha512-c51C+N+UHySoV2lgfWSwwmlnLnL0JWj/LzuZt9Ltk9ub1s2Y8cr6SQV5W3mqVH1egUceew6KZ8GyI4nwu+fhsw== + +esbuild-darwin-arm64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.12.tgz#8abae74c2956a8aa568fc52c78829338c4a4b988" + integrity sha512-JvAMtshP45Hd8A8wOzjkY1xAnTKTYuP/QUaKp5eUQGX+76GIie3fCdUUr2ZEKdvpSImNqxiZSIMziEiGB5oUmQ== + +esbuild-freebsd-64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.12.tgz#6ad2ab8c0364ee7dd2d6e324d876a8e60ae75d12" + integrity sha512-r6On/Skv9f0ZjTu6PW5o7pdXr8aOgtFOEURJZYf1XAJs0IQ+gW+o1DzXjVkIoT+n1cm3N/t1KRJfX71MPg/ZUA== + +esbuild-freebsd-arm64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.12.tgz#6f38155f4c300ac4c8adde1fde3cc6a4440a8294" + integrity sha512-F6LmI2Q1gii073kmBE3NOTt/6zLL5zvZsxNLF8PMAwdHc+iBhD1vzfI8uQZMJA1IgXa3ocr3L3DJH9fLGXy6Yw== + +esbuild-linux-32@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.12.tgz#b1d15e330188a8c21de75c3f0058628a3eefade7" + integrity sha512-U1UZwG3UIwF7/V4tCVAo/nkBV9ag5KJiJTt+gaCmLVWH3bPLX7y+fNlhIWZy8raTMnXhMKfaTvWZ9TtmXzvkuQ== + +esbuild-linux-64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.12.tgz#25bd64b66162b02348e32d8f12e4c9ee61f1d070" + integrity sha512-YpXSwtu2NxN3N4ifJxEdsgd6Q5d8LYqskrAwjmoCT6yQnEHJSF5uWcxv783HWN7lnGpJi9KUtDvYsnMdyGw71Q== + +esbuild-linux-arm64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.12.tgz#ba582298457cc5c9ac823a275de117620c06537f" + integrity sha512-sgDNb8kb3BVodtAlcFGgwk+43KFCYjnFOaOfJibXnnIojNWuJHpL6aQJ4mumzNWw8Rt1xEtDQyuGK9f+Y24jGA== + +esbuild-linux-arm@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.12.tgz#6bc81c957bff22725688cc6359c29a25765be09b" + integrity sha512-SyiT/JKxU6J+DY2qUiSLZJqCAftIt3uoGejZ0HDnUM2MGJqEGSGh7p1ecVL2gna3PxS4P+j6WAehCwgkBPXNIw== + +esbuild-linux-mips64le@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.12.tgz#ef3c4aba3e585d847cbade5945a8b4a5c62c7ce2" + integrity sha512-qQJHlZBG+QwVIA8AbTEtbvF084QgDi4DaUsUnA+EolY1bxrG+UyOuGflM2ZritGhfS/k7THFjJbjH2wIeoKA2g== + +esbuild-linux-ppc64le@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.12.tgz#a21fb64e80c38bef06122e48283990fc6db578e1" + integrity sha512-2dSnm1ldL7Lppwlo04CGQUpwNn5hGqXI38OzaoPOkRsBRWFBozyGxTFSee/zHFS+Pdh3b28JJbRK3owrrRgWNw== + +esbuild-netbsd-64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.12.tgz#1ea7fc8cfce88a20a4047b867ef184049a6641ae" + integrity sha512-D4raxr02dcRiQNbxOLzpqBzcJNFAdsDNxjUbKkDMZBkL54Z0vZh4LRndycdZAMcIdizC/l/Yp/ZsBdAFxc5nbA== + +esbuild-openbsd-64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.12.tgz#adde32f2f1b05dc4bd4fc544d6ea5a4379f9ca4d" + integrity sha512-KuLCmYMb2kh05QuPJ+va60bKIH5wHL8ypDkmpy47lzwmdxNsuySeCMHuTv5o2Af1RUn5KLO5ZxaZeq4GEY7DaQ== + +esbuild-sunos-64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.12.tgz#a7ecaf52b7364fbee76dc8aa707fa3e1cff3342c" + integrity sha512-jBsF+e0woK3miKI8ufGWKG3o3rY9DpHvCVRn5eburMIIE+2c+y3IZ1srsthKyKI6kkXLvV4Cf/E7w56kLipMXw== + +esbuild-windows-32@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.12.tgz#a8756033dc905c4b7bea19be69f7ee68809f8770" + integrity sha512-L9m4lLFQrFeR7F+eLZXG82SbXZfUhyfu6CexZEil6vm+lc7GDCE0Q8DiNutkpzjv1+RAbIGVva9muItQ7HVTkQ== + +esbuild-windows-64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.12.tgz#ae694aa66ca078acb8509b2da31197ed1f40f798" + integrity sha512-k4tX4uJlSbSkfs78W5d9+I9gpd+7N95W7H2bgOMFPsYREVJs31+Q2gLLHlsnlY95zBoPQMIzHooUIsixQIBjaQ== + +esbuild-windows-arm64@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.12.tgz#782c5a8bd6d717ea55aaafe648f9926ca36a4a88" + integrity sha512-2tTv/BpYRIvuwHpp2M960nG7uvL+d78LFW/ikPItO+2GfK51CswIKSetSpDii+cjz8e9iSPgs+BU4o8nWICBwQ== + +esbuild@0.13.12: + version "0.13.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.12.tgz#9cac641594bf03cf34145258c093d743ebbde7ca" + integrity sha512-vTKKUt+yoz61U/BbrnmlG9XIjwpdIxmHB8DlPR0AAW6OdS+nBQBci6LUHU2q9WbBobMEIQxxDpKbkmOGYvxsow== + optionalDependencies: + esbuild-android-arm64 "0.13.12" + esbuild-darwin-64 "0.13.12" + esbuild-darwin-arm64 "0.13.12" + esbuild-freebsd-64 "0.13.12" + esbuild-freebsd-arm64 "0.13.12" + esbuild-linux-32 "0.13.12" + esbuild-linux-64 "0.13.12" + esbuild-linux-arm "0.13.12" + esbuild-linux-arm64 "0.13.12" + esbuild-linux-mips64le "0.13.12" + esbuild-linux-ppc64le "0.13.12" + esbuild-netbsd-64 "0.13.12" + esbuild-openbsd-64 "0.13.12" + esbuild-sunos-64 "0.13.12" + esbuild-windows-32 "0.13.12" + esbuild-windows-64 "0.13.12" + esbuild-windows-arm64 "0.13.12" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"