Skip to content

Commit bede232

Browse files
alan-agius4mgechev
authored andcommitted
feat(@schematics/angular): add solutions style tsconfig structure
In version 3.9, TypeScript introduced the concept of "Solutions Style" tsconfig to improve developer experience. More info: https://devblogs.microsoft.com/typescript/announcing-typescript-3-9-rc/#solution-style-tsconfig Closes #17493 and closes #8138
1 parent 93e253b commit bede232

31 files changed

+607
-82
lines changed

packages/schematics/angular/application/files/tsconfig.app.json.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json",
2+
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json",
33
"compilerOptions": {
44
"outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/app",
55
"types": []

packages/schematics/angular/application/files/tsconfig.spec.json.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json",
2+
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json",
33
"compilerOptions": {
44
"outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/spec",
55
"types": [

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json",
2+
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json",
33
"compilerOptions": {
44
"outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/e2e",
55
"module": "commonjs",

packages/schematics/angular/e2e/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
url,
1919
} from '@angular-devkit/schematics';
2020
import { relativePathToWorkspaceRoot } from '../utility/paths';
21+
import { addTsConfigProjectReferences, verifyBaseTsConfigExists } from '../utility/tsconfig';
2122
import { getWorkspace, updateWorkspace } from '../utility/workspace';
2223
import { Builders } from '../utility/workspace-models';
2324
import { Schema as E2eOptions } from './schema';
@@ -31,6 +32,8 @@ export default function (options: E2eOptions): Rule {
3132
throw new SchematicsException(`Project name "${appProject}" doesn't not exist.`);
3233
}
3334

35+
verifyBaseTsConfigExists(host);
36+
3437
const root = join(normalize(project.root), 'e2e');
3538

3639
project.targets.add({
@@ -47,10 +50,11 @@ export default function (options: E2eOptions): Rule {
4750
},
4851
});
4952

53+
const e2eTsConfig = `${root}/tsconfig.json`;
5054
const lintTarget = project.targets.get('lint');
5155
if (lintTarget && lintTarget.options && Array.isArray(lintTarget.options.tsConfig)) {
5256
lintTarget.options.tsConfig =
53-
lintTarget.options.tsConfig.concat(`${root}/tsconfig.json`);
57+
lintTarget.options.tsConfig.concat(e2eTsConfig);
5458
}
5559

5660
return chain([
@@ -64,6 +68,9 @@ export default function (options: E2eOptions): Rule {
6468
}),
6569
move(root),
6670
])),
71+
addTsConfigProjectReferences([
72+
e2eTsConfig,
73+
]),
6774
]);
6875
};
6976
}

packages/schematics/angular/e2e/index_spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import { JsonParseMode, parseJson } from '@angular-devkit/core';
89
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
910
import { Schema as ApplicationOptions } from '../application/schema';
1011
import { Schema as WorkspaceOptions } from '../workspace/schema';
@@ -81,6 +82,19 @@ describe('Application Schematic', () => {
8182
expect(content).toMatch(/🌮-🌯/);
8283
});
8384

85+
it('should add reference in solution style tsconfig', async () => {
86+
const tree = await schematicRunner.runSchematicAsync('e2e', defaultOptions, applicationTree)
87+
.toPromise();
88+
89+
// tslint:disable-next-line:no-any
90+
const { references } = parseJson(tree.readContent('/tsconfig.json').toString(), JsonParseMode.Loose) as any;
91+
expect(references).toEqual([
92+
{ path: './projects/foo/tsconfig.app.json' },
93+
{ path: './projects/foo/tsconfig.spec.json' },
94+
{ path: './projects/foo/e2e/tsconfig.json' },
95+
]);
96+
});
97+
8498
describe('workspace config', () => {
8599
it('should add e2e targets for the app', async () => {
86100
const tree = await schematicRunner.runSchematicAsync('e2e', defaultOptions, applicationTree)

packages/schematics/angular/library/files/tsconfig.lib.json.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json",
2+
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json",
33
"compilerOptions": {
44
"outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/lib",
55
"target": "es2015",

packages/schematics/angular/library/files/tsconfig.spec.json.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json",
2+
"extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json",
33
"compilerOptions": {
44
"outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/spec",
55
"types": [

packages/schematics/angular/library/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { NodeDependencyType, addPackageJsonDependency } from '../utility/depende
2525
import { latestVersions } from '../utility/latest-versions';
2626
import { applyLintFix } from '../utility/lint-fix';
2727
import { relativePathToWorkspaceRoot } from '../utility/paths';
28+
import { addTsConfigProjectReferences, verifyBaseTsConfigExists } from '../utility/tsconfig';
2829
import { validateProjectName } from '../utility/validation';
2930
import { getWorkspace, updateWorkspace } from '../utility/workspace';
3031
import { Builders, ProjectType } from '../utility/workspace-models';
@@ -58,9 +59,9 @@ function updateJsonFile<T>(host: Tree, path: string, callback: UpdateJsonFn<T>):
5859
function updateTsConfig(packageName: string, ...paths: string[]) {
5960

6061
return (host: Tree) => {
61-
if (!host.exists('tsconfig.json')) { return host; }
62+
if (!host.exists('tsconfig.base.json')) { return host; }
6263

63-
return updateJsonFile(host, 'tsconfig.json', (tsconfig: TsConfigPartialType) => {
64+
return updateJsonFile(host, 'tsconfig.base.json', (tsconfig: TsConfigPartialType) => {
6465
if (!tsconfig.compilerOptions.paths) {
6566
tsconfig.compilerOptions.paths = {};
6667
}
@@ -73,7 +74,6 @@ function updateTsConfig(packageName: string, ...paths: string[]) {
7374
}
7475

7576
function addDependenciesToPackageJson() {
76-
7777
return (host: Tree) => {
7878
[
7979
{
@@ -174,6 +174,7 @@ export default function (options: LibraryOptions): Rule {
174174
const prefix = options.prefix;
175175

176176
validateProjectName(options.name);
177+
verifyBaseTsConfigExists(host);
177178

178179
// If scoped project (i.e. "@foo/bar"), convert projectDir to "foo/bar".
179180
const projectName = options.name;
@@ -239,6 +240,10 @@ export default function (options: LibraryOptions): Rule {
239240
path: sourceDir,
240241
project: options.name,
241242
}),
243+
addTsConfigProjectReferences([
244+
`${projectRoot}/tsconfig.lib.json`,
245+
`${projectRoot}/tsconfig.spec.json`,
246+
]),
242247
options.lintFix ? applyLintFix(sourceDir) : noop(),
243248
(_tree: Tree, context: SchematicContext) => {
244249
if (!options.skipPackageJson && !options.skipInstall) {

packages/schematics/angular/library/index_spec.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
// tslint:disable:no-big-function
9+
import { JsonParseMode, parseJson } from '@angular-devkit/core';
910
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
1011
import { getFileContent } from '../../angular/utility/test';
1112
import { Schema as ComponentOptions } from '../component/schema';
@@ -200,19 +201,19 @@ describe('Library Schematic', () => {
200201
});
201202
});
202203

203-
describe(`update tsconfig.json`, () => {
204+
describe(`update tsconfig.base.json`, () => {
204205
it(`should add paths mapping to empty tsconfig`, async () => {
205206
const tree = await schematicRunner.runSchematicAsync('library', defaultOptions, workspaceTree).toPromise();
206207

207-
const tsConfigJson = getJsonFileContent(tree, 'tsconfig.json');
208+
const tsConfigJson = getJsonFileContent(tree, 'tsconfig.base.json');
208209
expect(tsConfigJson.compilerOptions.paths.foo).toBeTruthy();
209210
expect(tsConfigJson.compilerOptions.paths.foo.length).toEqual(2);
210211
expect(tsConfigJson.compilerOptions.paths.foo[0]).toEqual('dist/foo/foo');
211212
expect(tsConfigJson.compilerOptions.paths.foo[1]).toEqual('dist/foo');
212213
});
213214

214215
it(`should append to existing paths mappings`, async () => {
215-
workspaceTree.overwrite('tsconfig.json', JSON.stringify({
216+
workspaceTree.overwrite('tsconfig.base.json', JSON.stringify({
216217
compilerOptions: {
217218
paths: {
218219
'unrelated': ['./something/else.ts'],
@@ -222,7 +223,7 @@ describe('Library Schematic', () => {
222223
}));
223224
const tree = await schematicRunner.runSchematicAsync('library', defaultOptions, workspaceTree).toPromise();
224225

225-
const tsConfigJson = getJsonFileContent(tree, 'tsconfig.json');
226+
const tsConfigJson = getJsonFileContent(tree, 'tsconfig.base.json');
226227
expect(tsConfigJson.compilerOptions.paths.foo).toBeTruthy();
227228
expect(tsConfigJson.compilerOptions.paths.foo.length).toEqual(3);
228229
expect(tsConfigJson.compilerOptions.paths.foo[1]).toEqual('dist/foo/foo');
@@ -235,7 +236,7 @@ describe('Library Schematic', () => {
235236
skipTsConfig: true,
236237
}, workspaceTree).toPromise();
237238

238-
const tsConfigJson = getJsonFileContent(tree, 'tsconfig.json');
239+
const tsConfigJson = getJsonFileContent(tree, 'tsconfig.base.json');
239240
expect(tsConfigJson.compilerOptions.paths).toBeUndefined();
240241
});
241242
});
@@ -264,12 +265,12 @@ describe('Library Schematic', () => {
264265
expect(pkgJson.name).toEqual(scopedName);
265266

266267
const tsConfigJson = JSON.parse(tree.readContent('/projects/myscope/mylib/tsconfig.spec.json'));
267-
expect(tsConfigJson.extends).toEqual('../../../tsconfig.json');
268+
expect(tsConfigJson.extends).toEqual('../../../tsconfig.base.json');
268269

269270
const cfg = JSON.parse(tree.readContent('/angular.json'));
270271
expect(cfg.projects['@myscope/mylib']).toBeDefined();
271272

272-
const rootTsCfg = JSON.parse(tree.readContent('/tsconfig.json'));
273+
const rootTsCfg = JSON.parse(tree.readContent('/tsconfig.base.json'));
273274
expect(rootTsCfg.compilerOptions.paths['@myscope/mylib']).toEqual(['dist/myscope/mylib/myscope-mylib', 'dist/myscope/mylib']);
274275

275276
const karmaConf = getFileContent(tree, '/projects/myscope/mylib/karma.conf.js');
@@ -314,9 +315,9 @@ describe('Library Schematic', () => {
314315
expect(buildOpt.tsConfig).toEqual('foo/tsconfig.lib.json');
315316

316317
const appTsConfig = JSON.parse(tree.readContent('/foo/tsconfig.lib.json'));
317-
expect(appTsConfig.extends).toEqual('../tsconfig.json');
318+
expect(appTsConfig.extends).toEqual('../tsconfig.base.json');
318319
const specTsConfig = JSON.parse(tree.readContent('/foo/tsconfig.spec.json'));
319-
expect(specTsConfig.extends).toEqual('../tsconfig.json');
320+
expect(specTsConfig.extends).toEqual('../tsconfig.base.json');
320321
});
321322

322323
it(`should add 'production' configuration`, async () => {
@@ -326,4 +327,16 @@ describe('Library Schematic', () => {
326327
const workspace = JSON.parse(tree.readContent('/angular.json'));
327328
expect(workspace.projects.foo.architect.build.configurations.production).toBeDefined();
328329
});
330+
331+
it('should add reference in solution style tsconfig', async () => {
332+
const tree = await schematicRunner.runSchematicAsync('library', defaultOptions, workspaceTree)
333+
.toPromise();
334+
335+
// tslint:disable-next-line:no-any
336+
const { references } = parseJson(tree.readContent('/tsconfig.json').toString(), JsonParseMode.Loose) as any;
337+
expect(references).toEqual([
338+
{ path: './projects/foo/tsconfig.lib.json' },
339+
{ path: './projects/foo/tsconfig.spec.json' },
340+
]);
341+
});
329342
});

packages/schematics/angular/library/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"skipTsConfig": {
4242
"type": "boolean",
4343
"default": false,
44-
"description": "When true, does not update \"tsconfig.json\" to add a path mapping for the new library. The path mapping is needed to use the library in an app, but can be disabled here to simplify development."
44+
"description": "When true, does not update \"tsconfig.base.json\" to add a path mapping for the new library. The path mapping is needed to use the library in an app, but can be disabled here to simplify development."
4545
},
4646
"lintFix": {
4747
"type": "boolean",

0 commit comments

Comments
 (0)