Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compiler): support ESM for isolatedModules: false #721

Merged
merged 1 commit into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions e2e/test-app-v10/jest-esm-uniso.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require('jest-preset-angular/ngcc-jest-processor');
const baseConfig = require('./jest.config');

/** @type {import('ts-jest/dist/types').ProjectConfigTsJest} */
module.exports = {
...baseConfig,
extensionsToTreatAsEsm: ['.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig-esm.spec.json',
stringifyContentPathRegex: '\\.html$',
useESM: true,
}
},
moduleNameMapper: {
...baseConfig.moduleNameMapper,
'tslib': '<rootDir>/node_modules/tslib/tslib.es6.js',
},
transformIgnorePatterns: ['node_modules/(?!tslib)'],
};
1 change: 1 addition & 0 deletions e2e/test-app-v10/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"test-cjs-uniso": "jest --clearCache && jest -c=jest-cjs-uniso.config.js",
"test-cjs-iso": "jest --clearCache && jest -c=jest-cjs-iso.config.js",
"test-esm-iso": "jest --clearCache && node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c=jest-esm-iso.config.js",
"test-esm-uniso": "jest --clearCache && node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c=jest-esm-uniso.config.js",
"lint": "ng lint"
},
"private": true,
Expand Down
20 changes: 20 additions & 0 deletions e2e/test-app-v11/jest-esm-uniso.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require('jest-preset-angular/ngcc-jest-processor');
const baseConfig = require('./jest.config');

/** @type {import('ts-jest/dist/types').ProjectConfigTsJest} */
module.exports = {
...baseConfig,
extensionsToTreatAsEsm: ['.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig-esm.spec.json',
stringifyContentPathRegex: '\\.html$',
useESM: true,
}
},
moduleNameMapper: {
...baseConfig.moduleNameMapper,
'tslib': '<rootDir>/node_modules/tslib/tslib.es6.js',
},
transformIgnorePatterns: ['node_modules/(?!tslib)'],
};
1 change: 1 addition & 0 deletions e2e/test-app-v11/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"test-cjs-uniso": "jest --clearCache && jest -c=jest-cjs-uniso.config.js",
"test-cjs-iso": "jest --clearCache && jest -c=jest-cjs-iso.config.js",
"test-esm-iso": "jest --clearCache && node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c=jest-esm-iso.config.js",
"test-esm-uniso": "jest --clearCache && node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c=jest-esm-uniso.config.js",
"lint": "ng lint"
},
"private": true,
Expand Down
20 changes: 20 additions & 0 deletions e2e/test-app-v9/jest-esm-uniso.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require('jest-preset-angular/ngcc-jest-processor');
const baseConfig = require('./jest.config');

/** @type {import('ts-jest/dist/types').ProjectConfigTsJest} */
module.exports = {
...baseConfig,
extensionsToTreatAsEsm: ['.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig-esm.spec.json',
stringifyContentPathRegex: '\\.html$',
useESM: true,
}
},
moduleNameMapper: {
...baseConfig.moduleNameMapper,
'tslib': '<rootDir>/node_modules/tslib/tslib.es6.js',
},
transformIgnorePatterns: ['node_modules/(?!tslib)'],
};
1 change: 1 addition & 0 deletions e2e/test-app-v9/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"test-cjs-uniso": "jest --clearCache && jest -c=jest-cjs-uniso.config.js",
"test-cjs-iso": "jest --clearCache && jest -c=jest-cjs-iso.config.js",
"test-esm-iso": "jest --clearCache && node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c=jest-esm-iso.config.js",
"test-esm-uniso": "jest --clearCache && node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c=jest-esm-uniso.config.js",
"lint": "ng lint"
},
"private": true,
Expand Down
13 changes: 13 additions & 0 deletions scripts/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,16 @@ const executeTest = (projectRealPath) => {
const cmdCjsUnIso = ['yarn', 'test-cjs-uniso'];
const cmdCjsIso = ['yarn', 'test-cjs-iso'];
const cmdESMIso = ['yarn', 'test-esm-iso'];
const cmdESMUnIso = ['yarn', 'test-esm-uniso'];
if (jestArgs.length) {
cmdCjsUnIso.push('--');
cmdCjsIso.push('--');
cmdESMIso.push('--');
cmdESMUnIso.push('--');
cmdCjsUnIso.push(...jestArgs);
cmdCjsIso.push(...jestArgs);
cmdESMIso.push(...jestArgs);
cmdESMUnIso.push(...jestArgs);
}

logger.log('STARTING NONE ISOLATED MODULES TESTS');
Expand All @@ -77,6 +80,16 @@ const executeTest = (projectRealPath) => {
env: process.env,
});

logger.log();
logger.log('starting the ESM tests using:', ...cmdESMUnIso);
logger.log();

execa.sync(cmdESMUnIso.shift(), cmdESMUnIso, {
cwd: projectRealPath,
stdio: 'inherit',
env: process.env,
});

logger.log();
logger.log('STARTING ISOLATED MODULES TESTS');
logger.log();
Expand Down
20 changes: 20 additions & 0 deletions src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NgJestCompiler with isolatedModule false should compile codes with useESM true 1`] = `
"import { __decorate } from \\"tslib\\";
import __NG_CLI_RESOURCE__0 from \\"./app.component.html\\";
import { Component } from '@angular/core';
let AppComponent = class AppComponent {
constructor() {
this.title = 'test-app-v10';
}
};
AppComponent = __decorate([
Component({
selector: 'app-root',
template: __NG_CLI_RESOURCE__0,
styles: []
})
], AppComponent);
export { AppComponent };
//# "
`;

exports[`NgJestCompiler with isolatedModule false should throw diagnostics error for new file which is: known by Program 1`] = `"src/__tests__/__mocks__/foo.component.ts(8,3): error TS2322: Type '\\"test-app-v10\\"' is not assignable to type 'number'."`;

exports[`NgJestCompiler with isolatedModule false should throw diagnostics error for new file which is: not known by Program 1`] = `"src/__tests__/__mocks__/foo.component.ts(8,3): error TS2322: Type '\\"test-app-v10\\"' is not assignable to type 'number'."`;
16 changes: 7 additions & 9 deletions src/__tests__/__snapshots__/replace-resources.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,22 @@ exports.AppComponent = AppComponent;
`;

exports[`Replace resources transformer with isolatedModules false should use replaceResources transformer from @angular/compiler-cli with useESM true 1`] = `
"\\"use strict\\";
Object.defineProperty(exports, \\"__esModule\\", { value: true });
exports.AppComponent = void 0;
const tslib_1 = require(\\"tslib\\");
const core_1 = require(\\"@angular/core\\");
"import { __decorate } from \\"tslib\\";
import __NG_CLI_RESOURCE__0 from \\"./app.component.html\\";
import { Component } from '@angular/core';
let AppComponent = class AppComponent {
constructor() {
this.title = 'test-app-v10';
}
};
AppComponent = tslib_1.__decorate([
core_1.Component({
AppComponent = __decorate([
Component({
selector: 'app-root',
template: require(\\"./app.component.html\\"),
template: __NG_CLI_RESOURCE__0,
styles: []
})
], AppComponent);
exports.AppComponent = AppComponent;
export { AppComponent };
//# "
`;

Expand Down
38 changes: 33 additions & 5 deletions src/__tests__/ng-jest-compiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFileSync } from 'fs';
import { join } from 'path';

import { jest } from '@jest/globals';
import { SOURCE_MAPPING_PREFIX } from 'ts-jest/dist/compiler/compiler-utils';
import ts from 'typescript';

import { NgJestCompiler } from '../compiler/ng-jest-compiler';
Expand Down Expand Up @@ -50,10 +51,15 @@ describe('NgJestCompiler', () => {
// @ts-expect-error testing purpose
expect(compiler._transpileModule).toHaveBeenCalled();
// @ts-expect-error testing purpose
const moduleKind = compiler._transpileModule.mock.calls[0][1].compilerOptions.module;
useESM
? expect(moduleKind).not.toEqual(ts.ModuleKind.CommonJS)
: expect(moduleKind).toEqual(ts.ModuleKind.CommonJS);
const { module, esModuleInterop, allowSyntheticDefaultImports } = compiler._transpileModule.mock.calls[0][1]
.compilerOptions as ts.CompilerOptions;
if (useESM) {
expect(module).not.toEqual(ts.ModuleKind.CommonJS);
expect(allowSyntheticDefaultImports).toEqual(true);
expect(esModuleInterop).toEqual(true);
} else {
expect(module).toEqual(ts.ModuleKind.CommonJS);
}
});
});

Expand Down Expand Up @@ -81,7 +87,6 @@ describe('NgJestCompiler', () => {
'exports.AppComponent = AppComponent;\n' +
'//# sourceMappingURL=app.component.js.map\n';

const ngJestConfig = new NgJestConfig(jestCfgStub);
const noErrorFileName = join(mockFolder, 'app.component.ts');
const noErrorFileContent = readFileSync(noErrorFileName, 'utf-8');
const hasErrorFileName = join(mockFolder, 'foo.component.ts');
Expand All @@ -90,6 +95,7 @@ describe('NgJestCompiler', () => {
test.each([noErrorFileName, undefined])(
'should return compiled result for new file which is not known or known by Program',
(fileName) => {
const ngJestConfig = new NgJestConfig(jestCfgStub);
ngJestConfig.parsedTsConfig = {
...ngJestConfig.parsedTsConfig,
rootNames: fileName ? [fileName] : [],
Expand All @@ -107,7 +113,28 @@ describe('NgJestCompiler', () => {
},
);

test('should compile codes with useESM true', () => {
const ngJestConfig = new NgJestConfig({
...jestCfgStub,
globals: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'ts-jest': {
...jestCfgStub.globals['ts-jest'],
useESM: true,
},
},
});
const compiler = new NgJestCompiler(ngJestConfig, new Map());

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const emittedResult = compiler.getCompiledOutput(noErrorFileName, noErrorFileContent, true)!;

// Source map is different based on file location which can fail on CI, so we only compare snapshot for js
expect(emittedResult.substring(0, emittedResult.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot();
});

test.each([hasErrorFileName, undefined])('should throw diagnostics error for new file which is', (fileName) => {
const ngJestConfig = new NgJestConfig(jestCfgStub);
ngJestConfig.parsedTsConfig = {
...ngJestConfig.parsedTsConfig,
rootNames: fileName ? [fileName] : [],
Expand All @@ -120,6 +147,7 @@ describe('NgJestCompiler', () => {
});

test('should not throw diagnostics error when shouldReportDiagnostics return false', () => {
const ngJestConfig = new NgJestConfig(jestCfgStub);
ngJestConfig.parsedTsConfig = {
...ngJestConfig.parsedTsConfig,
rootNames: [hasErrorFileName],
Expand Down
41 changes: 24 additions & 17 deletions src/compiler/ng-jest-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,30 @@ export class NgJestCompiler implements CompilerInstance {

getCompiledOutput(fileName: string, fileContent: string, supportsStaticESM: boolean): string {
const customTransformers = this.ngJestConfig.customTransformers;
let moduleKind = this._compilerOptions.module;
let esModuleInterop = this._compilerOptions.esModuleInterop;
let allowSyntheticDefaultImports = this._compilerOptions.allowSyntheticDefaultImports;
if (supportsStaticESM && this.ngJestConfig.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._program) {
const allDiagnostics = [];
const allDiagnostics: ts.Diagnostic[] = [];
if (!this._rootNames.includes(fileName)) {
this._logger.debug({ fileName }, 'getCompiledOutput: update memory host, rootFiles and Program');

Expand Down Expand Up @@ -95,28 +117,13 @@ export class NgJestCompiler implements CompilerInstance {
return '';
}
} else {
let moduleKind = this._compilerOptions.module;
if (supportsStaticESM && this.ngJestConfig.useESM) {
moduleKind =
!moduleKind ||
(moduleKind &&
![this._ts.ModuleKind.ES2015, this._ts.ModuleKind.ES2020, this._ts.ModuleKind.ESNext].includes(moduleKind))
? this._ts.ModuleKind.ESNext
: moduleKind;
} else {
moduleKind = this._ts.ModuleKind.CommonJS;
}

this._logger.debug({ fileName }, 'getCompiledOutput: compiling as isolated module');

const result: ts.TranspileOutput = this._transpileModule(
fileContent,
{
fileName,
compilerOptions: {
...this._compilerOptions,
module: moduleKind,
},
compilerOptions: this._compilerOptions,
},
customTransformers,
);
Expand Down