Skip to content

Commit 0fd3c55

Browse files
alan-agius4dgp1130
authored andcommittedMay 6, 2020
feat(@schematics/angular): update compiler options target and module settings
With this change we update the target and module settings of various compilation units. - We replace ES5 target in protractor. Protractor runs on Node.Js which support ES2018 - For applications we now use `ES2020` instead of `ESNext` as a module to avoid unexpected changes in behaviour This changes also adds a migration to update existing projects and also removes `module` from the Universal tsconfig as per #17352 to enable lazy loading on the server.
1 parent 13b0763 commit 0fd3c55

File tree

15 files changed

+283
-12
lines changed

15 files changed

+283
-12
lines changed
 

‎integration/angular_cli/e2e/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "../out-tsc/e2e",
55
"module": "commonjs",
6-
"target": "es5",
6+
"target": "es2018",
77
"types": [
88
"jasmine",
99
"jasminewd2",

‎integration/angular_cli/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"declaration": false,
88
"downlevelIteration": true,
99
"experimentalDecorators": true,
10-
"module": "esnext",
10+
"module": "es2020",
1111
"moduleResolution": "node",
1212
"importHelpers": true,
1313
"target": "es2015",

‎packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
194194
if (profilingEnabled) {
195195
extraPlugins.push(
196196
new debug.ProfilingPlugin({
197-
outputPath: path.resolve(root, `chrome-profiler-events${targetInFileName}.json`),
197+
outputPath: path.resolve(root, 'chrome-profiler-events.json'),
198198
}),
199199
);
200200
}
@@ -303,7 +303,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
303303
apply(compiler: Compiler) {
304304
compiler.hooks.emit.tap('angular-cli-stats', compilation => {
305305
const data = JSON.stringify(compilation.getStats().toJson('verbose'));
306-
compilation.assets[`stats${targetInFileName}.json`] = new RawSource(data);
306+
compilation.assets['stats.json'] = new RawSource(data);
307307
});
308308
}
309309
})(),

‎packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export enum ThresholdSeverity {
3232
}
3333

3434
enum DifferentialBuildType {
35+
// FIXME: this should match the actual file suffix and not hardcoded.
3536
ORIGINAL = 'es2015',
3637
DOWNLEVEL = 'es5',
3738
}

‎packages/angular_devkit/build_angular/test/hello-world-app/e2e/tsconfig.e2e.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "../out-tsc/e2e",
55
"module": "commonjs",
6-
"target": "es5",
6+
"target": "es2018",
77
"types": [
88
"jasmine",
99
"jasminewd2",

‎packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"emitDecoratorMetadata": true,
1111
"experimentalDecorators": true,
1212
"target": "es2015",
13-
"module": "esnext",
13+
"module": "es2020",
1414
"typeRoots": [
1515
"../node_modules/@types"
1616
],

‎packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"moduleResolution": "node",
99
"experimentalDecorators": true,
1010
"target": "es2015",
11-
"module": "esnext",
11+
"module": "es2020",
1212
"typeRoots": [
1313
"node_modules/@types"
1414
],

‎packages/angular_devkit/build_webpack/test/angular-app/src/tsconfig.app.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"extends": "../tsconfig.json",
33
"compilerOptions": {
44
"outDir": "../out-tsc/app",
5-
"module": "es2015",
5+
"module": "es2020",
66
"types": []
77
},
88
"exclude": [

‎packages/schematics/angular/e2e/files/tsconfig.json.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/e2e",
55
"module": "commonjs",
6-
"target": "es5",
6+
"target": "es2018",
77
"types": [
88
"jasmine",
99
"jasminewd2",

‎packages/schematics/angular/migrations/migration-collection.json

+5
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@
8989
"version": "10.0.0-beta.3",
9090
"factory": "./update-10/side-effects-package-json",
9191
"description": "Create a special 'package.json' file that is used to tell the tools and bundlers whether the code under the app directory is free of code with non-local side-effect."
92+
},
93+
"update-module-and-target-compiler-options": {
94+
"version": "10.0.0-beta.3",
95+
"factory": "./update-10/update-module-and-target-compiler-options",
96+
"description": "Update 'module' and 'target' TypeScript compiler options."
9297
}
9398
}
9499
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { dirname, join, normalize } from '@angular-devkit/core';
9+
import { Rule, Tree } from '@angular-devkit/schematics';
10+
import { findPropertyInAstObject, removePropertyInAstObject } from '../../utility/json-utils';
11+
import { getWorkspace } from '../../utility/workspace';
12+
import { Builders } from '../../utility/workspace-models';
13+
import { readJsonFileAsAstObject } from '../update-9/utils';
14+
15+
16+
interface ModuleAndTargetReplamenent {
17+
oldModule?: string;
18+
newModule?: string | false;
19+
oldTarget?: string;
20+
newTarget?: string;
21+
}
22+
23+
export default function (): Rule {
24+
return async host => {
25+
// Workspace level tsconfig
26+
updateModuleAndTarget(host, 'tsconfig.json', {
27+
oldModule: 'esnext',
28+
newModule: 'es2020',
29+
});
30+
31+
const workspace = await getWorkspace(host);
32+
// Find all tsconfig which are refereces used by builders
33+
for (const [, project] of workspace.projects) {
34+
for (const [, target] of project.targets) {
35+
// E2E builder doesn't reference a tsconfig but it uses one found in the root folder.
36+
if (target.builder === Builders.Protractor && typeof target.options?.protractorConfig === 'string') {
37+
const tsConfigPath = join(dirname(normalize(target.options.protractorConfig)), 'tsconfig.json');
38+
39+
updateModuleAndTarget(host, tsConfigPath, {
40+
oldTarget: 'es5',
41+
newTarget: 'es2018',
42+
});
43+
44+
continue;
45+
}
46+
47+
// Update all other known CLI builders that use a tsconfig
48+
const tsConfigs = [
49+
target.options || {},
50+
...Object.values(target.configurations || {}),
51+
]
52+
.filter(opt => typeof opt?.tsConfig === 'string')
53+
.map(opt => (opt as { tsConfig: string }).tsConfig);
54+
55+
const uniqueTsConfigs = [...new Set(tsConfigs)];
56+
57+
if (uniqueTsConfigs.length < 1) {
58+
continue;
59+
}
60+
61+
switch (target.builder as Builders) {
62+
case Builders.Server:
63+
uniqueTsConfigs.forEach(p => {
64+
updateModuleAndTarget(host, p, {
65+
oldModule: 'commonjs',
66+
// False will remove the module
67+
// NB: For server we no longer use commonjs because it is bundled using webpack which has it's own module system.
68+
// This ensures that lazy-loaded works on the server.
69+
newModule: false,
70+
});
71+
});
72+
break;
73+
case Builders.Karma:
74+
case Builders.Browser:
75+
case Builders.NgPackagr:
76+
uniqueTsConfigs.forEach(p => {
77+
updateModuleAndTarget(host, p, {
78+
oldModule: 'esnext',
79+
newModule: 'es2020',
80+
});
81+
});
82+
break;
83+
}
84+
}
85+
}
86+
};
87+
}
88+
89+
function updateModuleAndTarget(host: Tree, tsConfigPath: string, replacements: ModuleAndTargetReplamenent) {
90+
const jsonAst = readJsonFileAsAstObject(host, tsConfigPath);
91+
if (!jsonAst) {
92+
return;
93+
}
94+
95+
const compilerOptionsAst = findPropertyInAstObject(jsonAst, 'compilerOptions');
96+
if (compilerOptionsAst?.kind !== 'object') {
97+
return;
98+
}
99+
100+
const { oldTarget, newTarget, newModule, oldModule } = replacements;
101+
102+
const recorder = host.beginUpdate(tsConfigPath);
103+
if (newTarget) {
104+
const targetAst = findPropertyInAstObject(compilerOptionsAst, 'target');
105+
if (targetAst?.kind === 'string' && oldTarget === targetAst.value.toLowerCase()) {
106+
const offset = targetAst.start.offset + 1;
107+
recorder.remove(offset, targetAst.value.length);
108+
recorder.insertLeft(offset, newTarget);
109+
}
110+
}
111+
112+
if (newModule === false) {
113+
removePropertyInAstObject(recorder, compilerOptionsAst, 'module');
114+
} else if (newModule) {
115+
const moduleAst = findPropertyInAstObject(compilerOptionsAst, 'module');
116+
if (moduleAst?.kind === 'string' && oldModule === moduleAst.value.toLowerCase()) {
117+
const offset = moduleAst.start.offset + 1;
118+
recorder.remove(offset, moduleAst.value.length);
119+
recorder.insertLeft(offset, newModule);
120+
}
121+
}
122+
123+
host.commitUpdate(recorder);
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { JsonParseMode, parseJson } from '@angular-devkit/core';
9+
import { EmptyTree } from '@angular-devkit/schematics';
10+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
11+
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
12+
13+
describe('Migration to update target and module compiler options', () => {
14+
const schematicName = 'update-module-and-target-compiler-options';
15+
16+
const schematicRunner = new SchematicTestRunner(
17+
'migrations',
18+
require.resolve('../migration-collection.json'),
19+
);
20+
21+
function createJsonFile(tree: UnitTestTree, filePath: string, content: {}) {
22+
tree.create(filePath, JSON.stringify(content, undefined, 2));
23+
}
24+
25+
// tslint:disable-next-line: no-any
26+
function readJsonFile(tree: UnitTestTree, filePath: string): any {
27+
// tslint:disable-next-line: no-any
28+
return parseJson(tree.readContent(filePath).toString(), JsonParseMode.Loose) as any;
29+
}
30+
31+
function createWorkSpaceConfig(tree: UnitTestTree) {
32+
const angularConfig: WorkspaceSchema = {
33+
version: 1,
34+
projects: {
35+
app: {
36+
root: '',
37+
sourceRoot: 'src',
38+
projectType: ProjectType.Application,
39+
prefix: 'app',
40+
architect: {
41+
build: {
42+
builder: Builders.Browser,
43+
options: {
44+
tsConfig: 'src/tsconfig.app.json',
45+
main: '',
46+
polyfills: '',
47+
},
48+
configurations: {
49+
production: {
50+
tsConfig: 'src/tsconfig.app.prod.json',
51+
},
52+
},
53+
},
54+
test: {
55+
builder: Builders.Karma,
56+
options: {
57+
karmaConfig: '',
58+
tsConfig: 'src/tsconfig.spec.json',
59+
},
60+
},
61+
e2e: {
62+
builder: Builders.Protractor,
63+
options: {
64+
protractorConfig: 'src/e2e/protractor.conf.js',
65+
devServerTarget: '',
66+
},
67+
},
68+
server: {
69+
builder: Builders.Server,
70+
options: {
71+
tsConfig: 'src/tsconfig.server.json',
72+
outputPath: '',
73+
main: '',
74+
},
75+
},
76+
},
77+
},
78+
},
79+
};
80+
81+
createJsonFile(tree, 'angular.json', angularConfig);
82+
}
83+
84+
85+
let tree: UnitTestTree;
86+
beforeEach(() => {
87+
tree = new UnitTestTree(new EmptyTree());
88+
createWorkSpaceConfig(tree);
89+
90+
// Create tsconfigs
91+
const compilerOptions = { target: 'es2015', module: 'esnext' };
92+
93+
// Workspace
94+
createJsonFile(tree, 'tsconfig.json', { compilerOptions });
95+
96+
// Application
97+
createJsonFile(tree, 'src/tsconfig.app.json', { compilerOptions });
98+
createJsonFile(tree, 'src/tsconfig.app.prod.json', { compilerOptions });
99+
createJsonFile(tree, 'src/tsconfig.spec.json', { compilerOptions });
100+
101+
// E2E
102+
createJsonFile(tree, 'src/e2e/protractor.conf.js', '');
103+
createJsonFile(tree, 'src/e2e/tsconfig.json', { compilerOptions: { module: 'commonjs', target: 'es5' } });
104+
105+
// Universal
106+
createJsonFile(tree, 'src/tsconfig.server.json', { compilerOptions: { module: 'commonjs' } });
107+
});
108+
109+
it(`should update module and target in workspace 'tsconfig.json'`, async () => {
110+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
111+
const { module } = readJsonFile(newTree, 'tsconfig.json').compilerOptions;
112+
expect(module).toBe('es2020');
113+
});
114+
115+
it(`should update module and target in 'tsconfig.json' which is referenced in option`, async () => {
116+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
117+
const { module } = readJsonFile(newTree, 'src/tsconfig.spec.json').compilerOptions;
118+
expect(module).toBe('es2020');
119+
});
120+
121+
it(`should update module and target in 'tsconfig.json' which is referenced in a configuration`, async () => {
122+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
123+
const { module } = readJsonFile(newTree, 'src/tsconfig.app.prod.json').compilerOptions;
124+
expect(module).toBe('es2020');
125+
});
126+
127+
it(`should update target to es2018 in E2E 'tsconfig.json'`, async () => {
128+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
129+
const { module, target } = readJsonFile(newTree, 'src/e2e/tsconfig.json').compilerOptions;
130+
expect(module).toBe('commonjs');
131+
expect(target).toBe('es2018');
132+
});
133+
134+
135+
it(`should remove module in 'tsconfig.server.json'`, async () => {
136+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
137+
const { module, target } = readJsonFile(newTree, 'src/tsconfig.server.json').compilerOptions;
138+
expect(module).toBeUndefined();
139+
expect(target).toBeUndefined();
140+
});
141+
});

‎packages/schematics/angular/workspace/files/tsconfig.json.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"declaration": false,
1212
"downlevelIteration": true,
1313
"experimentalDecorators": true,
14-
"module": "esnext",
14+
"module": "es2020",
1515
"moduleResolution": "node",
1616
"importHelpers": true,
1717
"target": "es2015",

‎tests/legacy-cli/e2e/assets/1.7-project/e2e/tsconfig.e2e.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"outDir": "../out-tsc/e2e",
55
"baseUrl": "./",
66
"module": "commonjs",
7-
"target": "es5",
7+
"target": "es2018",
88
"types": [
99
"jasmine",
1010
"jasminewd2",

‎tests/legacy-cli/e2e/assets/7.0-project/e2e/tsconfig.e2e.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"compilerOptions": {
44
"outDir": "../out-tsc/app",
55
"module": "commonjs",
6-
"target": "es5",
6+
"target": "es2018",
77
"types": [
88
"jasmine",
99
"jasminewd2",

0 commit comments

Comments
 (0)
Please sign in to comment.