Skip to content

Commit

Permalink
perf: use esbuild to process .mjs files (#1142)
Browse files Browse the repository at this point in the history
Closes #1141
  • Loading branch information
ahnpnl authored Nov 6, 2021
1 parent 688de61 commit 5d3fe10
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 92 deletions.
1 change: 1 addition & 0 deletions e2e/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'<rootDir>/jest-globals',
'<rootDir>/path-mapping',
'<rootDir>/snapshot-serializers',
'<rootDir>/transform-mjs',
'<rootDir>/with-babel',
],
};
1 change: 1 addition & 0 deletions e2e/path-mapping/__tests__/path-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']`,
);
Expand Down
6 changes: 6 additions & 0 deletions e2e/transform-mjs/__tests__/transform-mjs.ts
Original file line number Diff line number Diff line change
@@ -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();
});
3 changes: 3 additions & 0 deletions e2e/transform-mjs/foo.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const pi = 3.14

export { pi }
12 changes: 12 additions & 0 deletions e2e/transform-mjs/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @type {import('ts-jest/dist/types').ProjectConfigTsJest} */
module.exports = {
displayName: 'transform-mjs',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/../tsconfig.json',
},
},
transform: {
'^.+\\.(ts|js|mjs|html)$': '<rootDir>/../../build/index.js',
},
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
49 changes: 49 additions & 0 deletions src/__tests__/__snapshots__/ng-jest-transformer.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -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",
},
]
`;
104 changes: 34 additions & 70 deletions src/__tests__/ng-jest-transformer.spec.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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',
{
Expand All @@ -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<unknown, any>).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<unknown, any>).mockClear();
});
});
31 changes: 9 additions & 22 deletions src/ng-jest-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 5d3fe10

Please sign in to comment.