Skip to content

Commit 5d3fe10

Browse files
authored
perf: use esbuild to process .mjs files (#1142)
Closes #1141
1 parent 688de61 commit 5d3fe10

File tree

10 files changed

+224
-92
lines changed

10 files changed

+224
-92
lines changed

e2e/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
'<rootDir>/jest-globals',
1111
'<rootDir>/path-mapping',
1212
'<rootDir>/snapshot-serializers',
13+
'<rootDir>/transform-mjs',
1314
'<rootDir>/with-babel',
1415
],
1516
};

e2e/path-mapping/__tests__/path-mapping.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { bar } from '@bar/bar-constant';
22

33
test('path mapping should work without being affected by resolver', async () => {
44
expect(bar).toBe(1);
5+
// @ts-expect-error testing purpose
56
await expect(import('@foo/foo-constant')).rejects.toMatchInlineSnapshot(
67
`[Error: Cannot find module '@foo/foo-constant' from '__tests__/path-mapping.ts']`,
78
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @ts-expect-error TypeScript < 4.5 doesn't support `.mjs` import
2+
import * as foo from '../foo.mjs';
3+
4+
test('should transform mjs files', async () => {
5+
expect(foo.pi).toBeDefined();
6+
});

e2e/transform-mjs/foo.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const pi = 3.14
2+
3+
export { pi }

e2e/transform-mjs/jest.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @type {import('ts-jest/dist/types').ProjectConfigTsJest} */
2+
module.exports = {
3+
displayName: 'transform-mjs',
4+
globals: {
5+
'ts-jest': {
6+
tsconfig: '<rootDir>/../tsconfig.json',
7+
},
8+
},
9+
transform: {
10+
'^.+\\.(ts|js|mjs|html)$': '<rootDir>/../../build/index.js',
11+
},
12+
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"update-e2e": "node scripts/update-e2e-deps.js"
4949
},
5050
"dependencies": {
51+
"esbuild": "0.13.12",
5152
"jest-environment-jsdom": "^27.0.0",
5253
"pretty-format": "^27.0.0",
5354
"ts-jest": "^27.0.0"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`NgJestTransformer should process successfully a mjs file to CommonJS codes 1`] = `
4+
Array [
5+
"
6+
const pi = parseFloat(3.124);
7+
8+
export { pi };
9+
",
10+
Object {
11+
"format": "cjs",
12+
"loader": "js",
13+
"sourcemap": false,
14+
"target": "es2015",
15+
},
16+
]
17+
`;
18+
19+
exports[`NgJestTransformer should process successfully a mjs file to CommonJS codes 2`] = `
20+
Array [
21+
"
22+
const pi = parseFloat(3.124);
23+
24+
export { pi };
25+
",
26+
Object {
27+
"format": "cjs",
28+
"loader": "js",
29+
"sourcemap": true,
30+
"target": "es2016",
31+
},
32+
]
33+
`;
34+
35+
exports[`NgJestTransformer should process successfully a mjs file to CommonJS codes 3`] = `
36+
Array [
37+
"
38+
const pi = parseFloat(3.124);
39+
40+
export { pi };
41+
",
42+
Object {
43+
"format": "cjs",
44+
"loader": "js",
45+
"sourcemap": true,
46+
"target": "es2015",
47+
},
48+
]
49+
`;
Lines changed: 34 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1+
import { transformSync } from 'esbuild';
2+
13
import { NgJestCompiler } from '../compiler/ng-jest-compiler';
24
import { NgJestConfig } from '../config/ng-jest-config';
35
import { NgJestTransformer } from '../ng-jest-transformer';
46

57
const tr = new NgJestTransformer();
68

9+
jest.mock('esbuild', () => {
10+
return {
11+
transformSync: jest.fn().mockReturnValue({
12+
code: 'bla bla',
13+
map: JSON.stringify({ version: 1, sourceContent: 'foo foo' }),
14+
}),
15+
};
16+
});
17+
718
describe('NgJestTransformer', () => {
819
test('should create NgJestCompiler and NgJestConfig instances', () => {
920
// @ts-expect-error testing purpose
@@ -22,43 +33,26 @@ describe('NgJestTransformer', () => {
2233
expect(cs).toBeInstanceOf(NgJestConfig);
2334
});
2435

25-
test('should process successfully a mjs file with CommonJS mode', () => {
36+
test.each([
37+
{
38+
tsconfig: {
39+
sourceMap: false,
40+
},
41+
},
42+
{
43+
tsconfig: {
44+
target: 'es2016',
45+
},
46+
},
47+
{
48+
tsconfig: {},
49+
},
50+
])('should process successfully a mjs file to CommonJS codes', ({ tsconfig }) => {
2651
const result = tr.process(
2752
`
2853
const pi = parseFloat(3.124);
2954
30-
export default pi;
31-
`,
32-
'foo.mjs',
33-
{
34-
config: {
35-
cwd: process.cwd(),
36-
extensionsToTreatAsEsm: [],
37-
testMatch: [],
38-
testRegex: [],
39-
},
40-
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
41-
);
42-
43-
expect(typeof result).toBe('object');
44-
// @ts-expect-error `code` is a property of `TransformSource`
45-
expect(result.code).toMatchInlineSnapshot(`
46-
"\\"use strict\\";
47-
Object.defineProperty(exports, \\"__esModule\\", { value: true });
48-
const pi = parseFloat(3.124);
49-
exports.default = pi;
50-
//# sourceMappingURL=foo.mjs.js.map"
51-
`);
52-
// @ts-expect-error `map` is a property of `TransformSource`
53-
expect(result.map).toBeDefined();
54-
});
55-
56-
test('should process successfully a mjs file with ESM mode', () => {
57-
const result = tr.process(
58-
`
59-
const pi = parseFloat(3.124);
60-
61-
export default pi;
55+
export { pi };
6256
`,
6357
'foo.mjs',
6458
{
@@ -69,50 +63,20 @@ describe('NgJestTransformer', () => {
6963
testRegex: [],
7064
globals: {
7165
'ts-jest': {
72-
useESM: true,
66+
tsconfig,
7367
},
7468
},
7569
},
76-
supportsStaticESM: true,
7770
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
7871
);
7972

80-
expect(typeof result).toBe('object');
73+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
74+
expect((transformSync as unknown as jest.MockInstance<unknown, any>).mock.calls[0]).toMatchSnapshot();
75+
// @ts-expect-error `code` is a property of `TransformSource`
76+
expect(result.code).toBeDefined();
8177
// @ts-expect-error `code` is a property of `TransformSource`
82-
expect(result.code).toMatchInlineSnapshot(`
83-
"const pi = parseFloat(3.124);
84-
export default pi;
85-
//# sourceMappingURL=foo.mjs.js.map"
86-
`);
87-
// @ts-expect-error `map` is a property of `TransformSource`
8878
expect(result.map).toBeDefined();
89-
});
90-
91-
test('should throw syntax error for mjs file with checkJs true', () => {
92-
expect(() =>
93-
tr.process(
94-
`
95-
const pi == parseFloat(3.124);
96-
97-
export default pi;
98-
`,
99-
'foo.mjs',
100-
{
101-
config: {
102-
cwd: process.cwd(),
103-
extensionsToTreatAsEsm: [],
104-
testMatch: [],
105-
testRegex: [],
106-
globals: {
107-
'ts-jest': {
108-
tsconfig: {
109-
checkJs: true,
110-
},
111-
},
112-
},
113-
},
114-
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
115-
),
116-
).toThrowError();
79+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
80+
(transformSync as unknown as jest.MockInstance<unknown, any>).mockClear();
11781
});
11882
});

src/ng-jest-transformer.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path';
22

33
import type { TransformedSource } from '@jest/transform';
44
import type { Config } from '@jest/types';
5+
import { transformSync } from 'esbuild';
56
import { ConfigSet } from 'ts-jest/dist/config/config-set';
67
import { TsJestTransformer } from 'ts-jest/dist/ts-jest-transformer';
78
import type { ProjectConfigTsJest, TransformOptionsTsJest } from 'ts-jest/dist/types';
@@ -26,33 +27,19 @@ export class NgJestTransformer extends TsJestTransformer {
2627
const configSet = this._createConfigSet(transformOptions.config);
2728
/**
2829
* TypeScript < 4.5 doesn't support compiling `.mjs` file by default when running `tsc` which throws error
29-
* ```
30-
* File '/Users/ahn/ts-jest-only-example/foo.mjs' has an unsupported extension. The only supported extensions are
31-
* '.ts', '.tsx', '.d.ts', '.js', '.jsx'.
32-
* ```
33-
* However, `transpileModule` API supports `.mjs` so we use it as a workaround.
3430
*/
3531
if (path.extname(filePath) === '.mjs') {
36-
const compilerOptions = configSet.parsedTsConfig.options;
37-
const compilerModule = configSet.compilerModule;
38-
const { outputText, sourceMapText, diagnostics } = compilerModule.transpileModule(fileContent, {
39-
compilerOptions: {
40-
...compilerOptions,
41-
module:
42-
transformOptions.supportsStaticESM && configSet.useESM
43-
? compilerModule.ModuleKind.ES2020
44-
: compilerModule.ModuleKind.CommonJS,
45-
},
46-
fileName: filePath,
47-
reportDiagnostics: configSet.shouldReportDiagnostics(filePath),
32+
const { target, sourceMap } = configSet.parsedTsConfig.options;
33+
const { code, map } = transformSync(fileContent, {
34+
loader: 'js',
35+
format: 'cjs',
36+
target: target === configSet.compilerModule.ScriptTarget.ES2015 ? 'es2015' : 'es2016',
37+
sourcemap: sourceMap,
4838
});
49-
if (diagnostics?.length) {
50-
configSet.raiseDiagnostics(diagnostics, filePath);
51-
}
5239

5340
return {
54-
code: outputText,
55-
map: sourceMapText,
41+
code,
42+
map,
5643
};
5744
} else {
5845
return super.process(fileContent, filePath, transformOptions);

0 commit comments

Comments
 (0)