diff --git a/integration/angular_cli/e2e/tsconfig.json b/integration/angular_cli/e2e/tsconfig.json index c92199cfd63f..6b87cc425b4d 100644 --- a/integration/angular_cli/e2e/tsconfig.json +++ b/integration/angular_cli/e2e/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../out-tsc/e2e", "module": "commonjs", diff --git a/integration/angular_cli/tsconfig.app.json b/integration/angular_cli/tsconfig.app.json index f758d9820d53..44795bd55a31 100644 --- a/integration/angular_cli/tsconfig.app.json +++ b/integration/angular_cli/tsconfig.app.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.json", + "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] diff --git a/integration/angular_cli/tsconfig.base.json b/integration/angular_cli/tsconfig.base.json new file mode 100644 index 000000000000..5015054494d9 --- /dev/null +++ b/integration/angular_cli/tsconfig.base.json @@ -0,0 +1,25 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es2015", + "module": "es2020", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + }, + "angularCompilerOptions": { + "enableIvy": true, + "disableTypeScriptVersionCheck": true + } +} diff --git a/integration/angular_cli/tsconfig.json b/integration/angular_cli/tsconfig.json index ac05639a2820..6444c9b8ff97 100644 --- a/integration/angular_cli/tsconfig.json +++ b/integration/angular_cli/tsconfig.json @@ -1,23 +1,13 @@ +// This tsConfig file is used by editors and TypeScript’s language server to improve development experience. +// Note: This should not be used to perform a compilation. { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "module": "es2020", - "moduleResolution": "node", - "importHelpers": true, - "target": "es2015", - "lib": [ - "es2018", - "dom" - ] - }, - "angularCompilerOptions": { - "fullTemplateTypeCheck": true, - "strictInjectionParameters": true - } + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] } diff --git a/integration/angular_cli/tsconfig.spec.json b/integration/angular_cli/tsconfig.spec.json index 6400fde7d544..1db2e6ee9c6e 100644 --- a/integration/angular_cli/tsconfig.spec.json +++ b/integration/angular_cli/tsconfig.spec.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.json", + "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "./out-tsc/spec", "types": [ diff --git a/packages/angular/cli/commands/version-impl.ts b/packages/angular/cli/commands/version-impl.ts index a840cd3955a1..e6cb877e1337 100644 --- a/packages/angular/cli/commands/version-impl.ts +++ b/packages/angular/cli/commands/version-impl.ts @@ -194,7 +194,7 @@ export class VersionCommand extends Command { private getIvyWorkspace(): string { try { - const content = fs.readFileSync(path.resolve(this.workspace.root, 'tsconfig.json'), 'utf-8'); + const content = fs.readFileSync(path.resolve(this.workspace.root, 'tsconfig.base.json'), 'utf-8'); const tsConfig = parseJson(content, JsonParseMode.Loose); if (!isJsonObject(tsConfig)) { return ''; diff --git a/packages/angular_devkit/build_angular/src/browser/specs/allow-js_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/allow-js_spec.ts index 4e447355493b..3e41281d152d 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/allow-js_spec.ts +++ b/packages/angular_devkit/build_angular/src/browser/specs/allow-js_spec.ts @@ -29,7 +29,7 @@ describe('Browser Builder allow js', () => { }); host.replaceInFile( - 'tsconfig.json', + 'tsconfig.base.json', '"target": "es2015"', '"target": "es5", "allowJs": true', ); @@ -54,7 +54,7 @@ describe('Browser Builder allow js', () => { }); host.replaceInFile( - 'tsconfig.json', + 'tsconfig.base.json', '"target": "es2015"', '"target": "es5", "allowJs": true', ); @@ -81,7 +81,7 @@ describe('Browser Builder allow js', () => { }); host.replaceInFile( - 'tsconfig.json', + 'tsconfig.base.json', '"target": "es2015"', '"target": "es5", "allowJs": true', ); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/differential_loading_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/differential_loading_spec.ts index b8df000b13a7..4db4ddb29b83 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/differential_loading_spec.ts +++ b/packages/angular_devkit/build_angular/src/browser/specs/differential_loading_spec.ts @@ -64,7 +64,7 @@ describe('Browser Builder with differential loading', () => { it('emits all the neccessary files for target of ES2016', async () => { host.replaceInFile( - 'tsconfig.json', + 'tsconfig.base.json', '"target": "es2015",', `"target": "es2016",`, ); @@ -106,7 +106,7 @@ describe('Browser Builder with differential loading', () => { it('emits all the neccessary files for target of ESNext', async () => { host.replaceInFile( - 'tsconfig.json', + 'tsconfig.base.json', '"target": "es2015",', `"target": "esnext",`, ); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/optimization-level_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/optimization-level_spec.ts index f5025f2d9dcb..5f350cc6cf1f 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/optimization-level_spec.ts +++ b/packages/angular_devkit/build_angular/src/browser/specs/optimization-level_spec.ts @@ -27,7 +27,7 @@ describe('Browser Builder optimization level', () => { }); it('tsconfig target changes optimizations to use ES2015', async () => { - host.replaceInFile('tsconfig.json', '"target": "es5"', '"target": "es2015"'); + host.replaceInFile('tsconfig.base.json', '"target": "es5"', '"target": "es2015"'); const overrides = { optimization: true }; const { files } = await browserBuild(architect, host, target, overrides); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/resolve-json-module_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/resolve-json-module_spec.ts index 373ac3419984..c6ca2e7c9558 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/resolve-json-module_spec.ts +++ b/packages/angular_devkit/build_angular/src/browser/specs/resolve-json-module_spec.ts @@ -29,7 +29,7 @@ describe('Browser Builder resolve json module', () => { }); host.replaceInFile( - 'tsconfig.json', + 'tsconfig.base.json', '"target": "es2015"', '"target": "es5", "resolveJsonModule": true', ); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/tsconfig-paths_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/tsconfig-paths_spec.ts index 8e31ff766798..882550dc586b 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/tsconfig-paths_spec.ts +++ b/packages/angular_devkit/build_angular/src/browser/specs/tsconfig-paths_spec.ts @@ -22,7 +22,7 @@ describe('Browser Builder tsconfig paths', () => { it('works', async () => { host.replaceInFile('src/app/app.module.ts', './app.component', '@root/app/app.component'); - host.replaceInFile('tsconfig.json', /"baseUrl": ".\/",/, ` + host.replaceInFile('tsconfig.base.json', /"baseUrl": ".\/",/, ` "baseUrl": "./", "paths": { "@root/*": [ @@ -40,7 +40,7 @@ describe('Browser Builder tsconfig paths', () => { 'src/app/shared/meaning.ts': 'export var meaning = 42;', 'src/app/shared/index.ts': `export * from './meaning'`, }); - host.replaceInFile('tsconfig.json', /"baseUrl": ".\/",/, ` + host.replaceInFile('tsconfig.base.json', /"baseUrl": ".\/",/, ` "baseUrl": "./", "paths": { "@shared": [ diff --git a/packages/angular_devkit/build_angular/src/browser/specs/web-worker_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/web-worker_spec.ts index a0fcc373f828..19d9426a7f23 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/web-worker_spec.ts +++ b/packages/angular_devkit/build_angular/src/browser/specs/web-worker_spec.ts @@ -57,7 +57,7 @@ describe('Browser Builder Web Worker support', () => { // tests, and integration with other build targets. './src/tsconfig.worker.json': ` { - "extends": "../tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../out-tsc/worker", "lib": [ @@ -73,7 +73,7 @@ describe('Browser Builder Web Worker support', () => { // Alter the app tsconfig to not include *.worker.ts files. './src/tsconfig.app.json': ` { - "extends": "../tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../out-tsc/worker", "types": [] diff --git a/packages/angular_devkit/build_angular/src/karma/code-coverage_spec.ts b/packages/angular_devkit/build_angular/src/karma/code-coverage_spec.ts index 5514beddd702..8a2e2f350e28 100644 --- a/packages/angular_devkit/build_angular/src/karma/code-coverage_spec.ts +++ b/packages/angular_devkit/build_angular/src/karma/code-coverage_spec.ts @@ -87,7 +87,7 @@ describe('Karma Builder code coverage', () => { host.writeMultipleFiles(files); - host.replaceInFile('tsconfig.json', /"baseUrl": ".\/",/, ` + host.replaceInFile('tsconfig.base.json', /"baseUrl": ".\/",/, ` "baseUrl": "./", "paths": { "my-lib": [ diff --git a/packages/angular_devkit/build_angular/src/karma/works_spec.ts b/packages/angular_devkit/build_angular/src/karma/works_spec.ts index 9d8a5caf0cce..3f8ca86c5ee9 100644 --- a/packages/angular_devkit/build_angular/src/karma/works_spec.ts +++ b/packages/angular_devkit/build_angular/src/karma/works_spec.ts @@ -41,7 +41,7 @@ describe('Karma Builder', () => { }); it('supports ES2015 target', async () => { - host.replaceInFile('tsconfig.json', '"target": "es5"', '"target": "es2015"'); + host.replaceInFile('tsconfig.base.json', '"target": "es5"', '"target": "es2015"'); const run = await architect.scheduleTarget(karmaTargetSpec); diff --git a/packages/angular_devkit/build_angular/src/test-utils.ts b/packages/angular_devkit/build_angular/src/test-utils.ts index 9a1ae6f738f9..9f8e478fb2cb 100644 --- a/packages/angular_devkit/build_angular/src/test-utils.ts +++ b/packages/angular_devkit/build_angular/src/test-utils.ts @@ -55,7 +55,7 @@ export async function createArchitect(workspaceRoot: Path) { // Set AOT compilation to use VE if needed. if (veEnabled) { - host.replaceInFile('tsconfig.json', `"enableIvy": true,`, `"enableIvy": false,`); + host.replaceInFile('tsconfig.base.json', `"enableIvy": true,`, `"enableIvy": false,`); } return { diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/e2e/tsconfig.e2e.json b/packages/angular_devkit/build_angular/test/hello-world-app/e2e/tsconfig.e2e.json index c92199cfd63f..6b87cc425b4d 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/e2e/tsconfig.e2e.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/e2e/tsconfig.e2e.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../out-tsc/e2e", "module": "commonjs", diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json index f761e8b2c4fc..0f688b32598a 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../out-tsc/app", "types": [] diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json index de7733630eb2..0c06d472bf55 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../out-tsc/spec", "types": [ diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.base.json b/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.base.json new file mode 100644 index 000000000000..5015054494d9 --- /dev/null +++ b/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.base.json @@ -0,0 +1,25 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es2015", + "module": "es2020", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + }, + "angularCompilerOptions": { + "enableIvy": true, + "disableTypeScriptVersionCheck": true + } +} diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json b/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json index 0e3088946b77..c41356ea00a8 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json @@ -1,26 +1,19 @@ +// This tsConfig file is used by editors and TypeScript’s language server to improve development experience. +// Note: This should not be used to perform a compilation. { - "compileOnSave": false, - "compilerOptions": { - "preserveSymlinks": true, - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es2015", - "module": "es2020", - "typeRoots": [ - "../node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] - }, - "angularCompilerOptions": { - "enableIvy": true, - "disableTypeScriptVersionCheck": true - } + "files": [], + "references": [ + { + "path": "./src/tsconfig.app.json" + }, + { + "path": "./src/tsconfig.spec.json" + }, + { + "path": "./src/tsconfig.server.json" + }, + { + "path": "./e2e/tsconfig.e2e.json" + } + ] } diff --git a/packages/angular_devkit/build_ng_packagr/src/build/index_spec.ts b/packages/angular_devkit/build_ng_packagr/src/build/index_spec.ts index c06fcab9422b..01f8edd7a25c 100644 --- a/packages/angular_devkit/build_ng_packagr/src/build/index_spec.ts +++ b/packages/angular_devkit/build_ng_packagr/src/build/index_spec.ts @@ -50,7 +50,7 @@ describe('NgPackagr Builder', () => { // Set AOT compilation to use VE if needed. if (veEnabled) { - host.replaceInFile('tsconfig.json', `"enableIvy": true,`, `"enableIvy": false,`); + host.replaceInFile('tsconfig.base.json', `"enableIvy": true,`, `"enableIvy": false,`); } }); diff --git a/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.lib.json b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.lib.json index e9e014ba574d..fbcea14bb9db 100644 --- a/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.lib.json +++ b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.lib.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "target": "es2015", "declaration": true, diff --git a/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.spec.json b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.spec.json index 16da33db072c..a9ea5ea11493 100644 --- a/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.spec.json +++ b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/projects/lib/tsconfig.spec.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "../../out-tsc/spec", "types": [ diff --git a/packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.base.json b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.base.json new file mode 100644 index 000000000000..2c5d470a7258 --- /dev/null +++ b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.base.json @@ -0,0 +1,24 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "experimentalDecorators": true, + "target": "es2015", + "module": "es2020", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + }, + "angularCompilerOptions": { + "enableIvy": true, + "disableTypeScriptVersionCheck": true + } +} diff --git a/packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.json b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.json index 2c5d470a7258..3706a3e29ba4 100644 --- a/packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.json +++ b/packages/angular_devkit/build_ng_packagr/test/ng-packaged/tsconfig.json @@ -1,24 +1,13 @@ +// This tsConfig file is used by editors and TypeScript’s language server to improve development experience. +// Note: This should not be used to perform a compilation. { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "moduleResolution": "node", - "experimentalDecorators": true, - "target": "es2015", - "module": "es2020", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] - }, - "angularCompilerOptions": { - "enableIvy": true, - "disableTypeScriptVersionCheck": true - } + "files": [], + "references": [ + { + "path": "./projects/lib/tsconfig.lib.json" + }, + { + "path": "./projects/lib/tsconfig.spec.json" + } + ] } diff --git a/packages/angular_devkit/build_webpack/test/basic-app/tsconfig.json b/packages/angular_devkit/build_webpack/test/basic-app/tsconfig.json new file mode 100644 index 000000000000..707f685dd4bc --- /dev/null +++ b/packages/angular_devkit/build_webpack/test/basic-app/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "experimentalDecorators": true, + "target": "es2015", + "module": "esnext", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + }, + "angularCompilerOptions": { + "disableTypeScriptVersionCheck": true + } +} diff --git a/packages/schematics/angular/application/files/tsconfig.app.json.template b/packages/schematics/angular/application/files/tsconfig.app.json.template index 3b0c3645d228..e007d503b915 100644 --- a/packages/schematics/angular/application/files/tsconfig.app.json.template +++ b/packages/schematics/angular/application/files/tsconfig.app.json.template @@ -1,5 +1,5 @@ { - "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json", + "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json", "compilerOptions": { "outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/app", "types": [] diff --git a/packages/schematics/angular/application/files/tsconfig.spec.json.template b/packages/schematics/angular/application/files/tsconfig.spec.json.template index 72aa864c8732..d931b9c920b2 100644 --- a/packages/schematics/angular/application/files/tsconfig.spec.json.template +++ b/packages/schematics/angular/application/files/tsconfig.spec.json.template @@ -1,5 +1,5 @@ { - "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json", + "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json", "compilerOptions": { "outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/spec", "types": [ diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index 06f2b52a74db..66f6e85d4e0f 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -38,6 +38,7 @@ import { findPropertyInAstObject, insertPropertyInAstObjectInOrder } from '../ut import { latestVersions } from '../utility/latest-versions'; import { applyLintFix } from '../utility/lint-fix'; import { relativePathToWorkspaceRoot } from '../utility/paths'; +import { addTsConfigProjectReferences, verifyBaseTsConfigExists } from '../utility/tsconfig'; import { validateProjectName } from '../utility/validation'; import { getWorkspace, updateWorkspace } from '../utility/workspace'; import { Builders, ProjectType } from '../utility/workspace-models'; @@ -311,7 +312,6 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul }); }); } - function minimalPathFilter(path: string): boolean { const toRemoveList = /(test.ts|tsconfig.spec.json|karma.conf.js|tslint.json).template$/; @@ -319,11 +319,14 @@ function minimalPathFilter(path: string): boolean { } export default function (options: ApplicationOptions): Rule { - return async (host: Tree, context: SchematicContext) => { + return async (host: Tree) => { if (!options.name) { throw new SchematicsException(`Invalid options, "name" is required.`); } + validateProjectName(options.name); + verifyBaseTsConfigExists(host); + const appRootSelector = `${options.prefix}-root`; const componentOptions: Partial = !options.minimal ? { @@ -344,7 +347,7 @@ export default function (options: ApplicationOptions): Rule { const newProjectRoot = workspace.extensions.newProjectRoot as (string | undefined) || ''; const isRootApp = options.projectRoot !== undefined; const appDir = isRootApp - ? options.projectRoot as string + ? normalize(options.projectRoot || '') : join(normalize(newProjectRoot), options.name); const sourceDir = `${appDir}/src/app`; @@ -402,6 +405,10 @@ export default function (options: ApplicationOptions): Rule { }), move(sourceDir), ]), MergeStrategy.Overwrite), + addTsConfigProjectReferences([ + join(appDir, 'tsconfig.app.json'), + join(appDir, 'tsconfig.spec.json'), + ]), options.minimal ? noop() : schematic('e2e', e2eOptions), options.skipPackageJson ? noop() : addDependenciesToPackageJson(options), options.lintFix ? applyLintFix(appDir) : noop(), diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index f1d2865b44e0..f9e0d905c5fc 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ // tslint:disable:no-big-function +import { JsonParseMode, parseJson } from '@angular-devkit/core'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { latestVersions } from '../utility/latest-versions'; import { getFileContent } from '../utility/test'; @@ -74,6 +75,19 @@ describe('Application Schematic', () => { expect(workspace.defaultProject).toBe('foo'); }); + it('should add references in solution style tsconfig', async () => { + const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree) + .toPromise(); + + // tslint:disable-next-line:no-any + const { references } = parseJson(tree.readContent('/tsconfig.json').toString(), JsonParseMode.Loose) as any; + expect(references).toEqual([ + { path: './projects/foo/tsconfig.app.json' }, + { path: './projects/foo/tsconfig.spec.json' }, + { path: './projects/foo/e2e/tsconfig.json' }, + ]); + }); + it('should set the prefix to app if none is set', async () => { const options = { ...defaultOptions }; @@ -138,10 +152,10 @@ describe('Application Schematic', () => { .toPromise(); let path = '/projects/foo/tsconfig.app.json'; let content = tree.readContent(path); - expect(content).toMatch('../../tsconfig.json'); + expect(content).toMatch('../../tsconfig.base.json'); path = '/projects/foo/tsconfig.spec.json'; content = tree.readContent(path); - expect(content).toMatch('../../tsconfig.json'); + expect(content).toMatch('../../tsconfig.base.json'); const specTsConfig = JSON.parse(content); expect(specTsConfig.files).toEqual(['src/test.ts', 'src/polyfills.ts']); }); @@ -371,9 +385,9 @@ describe('Application Schematic', () => { const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree) .toPromise(); const appTsConfig = JSON.parse(tree.readContent('/tsconfig.app.json')); - expect(appTsConfig.extends).toEqual('./tsconfig.json'); + expect(appTsConfig.extends).toEqual('./tsconfig.base.json'); const specTsConfig = JSON.parse(tree.readContent('/tsconfig.spec.json')); - expect(specTsConfig.extends).toEqual('./tsconfig.json'); + expect(specTsConfig.extends).toEqual('./tsconfig.base.json'); expect(specTsConfig.files).toEqual(['src/test.ts', 'src/polyfills.ts']); }); @@ -414,9 +428,9 @@ describe('Application Schematic', () => { expect(buildOpt.tsConfig).toEqual('foo/tsconfig.app.json'); const appTsConfig = JSON.parse(tree.readContent('/foo/tsconfig.app.json')); - expect(appTsConfig.extends).toEqual('../tsconfig.json'); + expect(appTsConfig.extends).toEqual('../tsconfig.base.json'); const specTsConfig = JSON.parse(tree.readContent('/foo/tsconfig.spec.json')); - expect(specTsConfig.extends).toEqual('../tsconfig.json'); + expect(specTsConfig.extends).toEqual('../tsconfig.base.json'); }); }); diff --git a/packages/schematics/angular/e2e/files/tsconfig.json.template b/packages/schematics/angular/e2e/files/tsconfig.json.template index bb011f580373..99850cb06ec1 100644 --- a/packages/schematics/angular/e2e/files/tsconfig.json.template +++ b/packages/schematics/angular/e2e/files/tsconfig.json.template @@ -1,5 +1,5 @@ { - "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json", + "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json", "compilerOptions": { "outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/e2e", "module": "commonjs", diff --git a/packages/schematics/angular/e2e/index.ts b/packages/schematics/angular/e2e/index.ts index e26e20cfe510..94d67c48c4f1 100644 --- a/packages/schematics/angular/e2e/index.ts +++ b/packages/schematics/angular/e2e/index.ts @@ -18,6 +18,7 @@ import { url, } from '@angular-devkit/schematics'; import { relativePathToWorkspaceRoot } from '../utility/paths'; +import { addTsConfigProjectReferences, verifyBaseTsConfigExists } from '../utility/tsconfig'; import { getWorkspace, updateWorkspace } from '../utility/workspace'; import { Builders } from '../utility/workspace-models'; import { Schema as E2eOptions } from './schema'; @@ -31,6 +32,8 @@ export default function (options: E2eOptions): Rule { throw new SchematicsException(`Project name "${appProject}" doesn't not exist.`); } + verifyBaseTsConfigExists(host); + const root = join(normalize(project.root), 'e2e'); project.targets.add({ @@ -47,10 +50,11 @@ export default function (options: E2eOptions): Rule { }, }); + const e2eTsConfig = `${root}/tsconfig.json`; const lintTarget = project.targets.get('lint'); if (lintTarget && lintTarget.options && Array.isArray(lintTarget.options.tsConfig)) { lintTarget.options.tsConfig = - lintTarget.options.tsConfig.concat(`${root}/tsconfig.json`); + lintTarget.options.tsConfig.concat(e2eTsConfig); } return chain([ @@ -64,6 +68,9 @@ export default function (options: E2eOptions): Rule { }), move(root), ])), + addTsConfigProjectReferences([ + e2eTsConfig, + ]), ]); }; } diff --git a/packages/schematics/angular/e2e/index_spec.ts b/packages/schematics/angular/e2e/index_spec.ts index 4ef92e8f6467..fb223f156e37 100644 --- a/packages/schematics/angular/e2e/index_spec.ts +++ b/packages/schematics/angular/e2e/index_spec.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import { JsonParseMode, parseJson } from '@angular-devkit/core'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { Schema as ApplicationOptions } from '../application/schema'; import { Schema as WorkspaceOptions } from '../workspace/schema'; @@ -81,6 +82,19 @@ describe('Application Schematic', () => { expect(content).toMatch(/🌮-🌯/); }); + it('should add reference in solution style tsconfig', async () => { + const tree = await schematicRunner.runSchematicAsync('e2e', defaultOptions, applicationTree) + .toPromise(); + + // tslint:disable-next-line:no-any + const { references } = parseJson(tree.readContent('/tsconfig.json').toString(), JsonParseMode.Loose) as any; + expect(references).toEqual([ + { path: './projects/foo/tsconfig.app.json' }, + { path: './projects/foo/tsconfig.spec.json' }, + { path: './projects/foo/e2e/tsconfig.json' }, + ]); + }); + describe('workspace config', () => { it('should add e2e targets for the app', async () => { const tree = await schematicRunner.runSchematicAsync('e2e', defaultOptions, applicationTree) diff --git a/packages/schematics/angular/library/files/tsconfig.lib.json.template b/packages/schematics/angular/library/files/tsconfig.lib.json.template index 7518a4c87974..8f7b94ed501a 100644 --- a/packages/schematics/angular/library/files/tsconfig.lib.json.template +++ b/packages/schematics/angular/library/files/tsconfig.lib.json.template @@ -1,5 +1,5 @@ { - "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json", + "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json", "compilerOptions": { "outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/lib", "target": "es2015", diff --git a/packages/schematics/angular/library/files/tsconfig.spec.json.template b/packages/schematics/angular/library/files/tsconfig.spec.json.template index 24373a8fc395..0eddf40127ca 100644 --- a/packages/schematics/angular/library/files/tsconfig.spec.json.template +++ b/packages/schematics/angular/library/files/tsconfig.spec.json.template @@ -1,5 +1,5 @@ { - "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json", + "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json", "compilerOptions": { "outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/spec", "types": [ diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index dfa719e9fd83..2ed7ef293354 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -25,6 +25,7 @@ import { NodeDependencyType, addPackageJsonDependency } from '../utility/depende import { latestVersions } from '../utility/latest-versions'; import { applyLintFix } from '../utility/lint-fix'; import { relativePathToWorkspaceRoot } from '../utility/paths'; +import { addTsConfigProjectReferences, verifyBaseTsConfigExists } from '../utility/tsconfig'; import { validateProjectName } from '../utility/validation'; import { getWorkspace, updateWorkspace } from '../utility/workspace'; import { Builders, ProjectType } from '../utility/workspace-models'; @@ -58,9 +59,9 @@ function updateJsonFile(host: Tree, path: string, callback: UpdateJsonFn): function updateTsConfig(packageName: string, ...paths: string[]) { return (host: Tree) => { - if (!host.exists('tsconfig.json')) { return host; } + if (!host.exists('tsconfig.base.json')) { return host; } - return updateJsonFile(host, 'tsconfig.json', (tsconfig: TsConfigPartialType) => { + return updateJsonFile(host, 'tsconfig.base.json', (tsconfig: TsConfigPartialType) => { if (!tsconfig.compilerOptions.paths) { tsconfig.compilerOptions.paths = {}; } @@ -73,7 +74,6 @@ function updateTsConfig(packageName: string, ...paths: string[]) { } function addDependenciesToPackageJson() { - return (host: Tree) => { [ { @@ -174,6 +174,7 @@ export default function (options: LibraryOptions): Rule { const prefix = options.prefix; validateProjectName(options.name); + verifyBaseTsConfigExists(host); // If scoped project (i.e. "@foo/bar"), convert projectDir to "foo/bar". const projectName = options.name; @@ -239,6 +240,10 @@ export default function (options: LibraryOptions): Rule { path: sourceDir, project: options.name, }), + addTsConfigProjectReferences([ + `${projectRoot}/tsconfig.lib.json`, + `${projectRoot}/tsconfig.spec.json`, + ]), options.lintFix ? applyLintFix(sourceDir) : noop(), (_tree: Tree, context: SchematicContext) => { if (!options.skipPackageJson && !options.skipInstall) { diff --git a/packages/schematics/angular/library/index_spec.ts b/packages/schematics/angular/library/index_spec.ts index 977dd8bf9043..4a93b809e220 100644 --- a/packages/schematics/angular/library/index_spec.ts +++ b/packages/schematics/angular/library/index_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ // tslint:disable:no-big-function +import { JsonParseMode, parseJson } from '@angular-devkit/core'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { getFileContent } from '../../angular/utility/test'; import { Schema as ComponentOptions } from '../component/schema'; @@ -200,11 +201,11 @@ describe('Library Schematic', () => { }); }); - describe(`update tsconfig.json`, () => { + describe(`update tsconfig.base.json`, () => { it(`should add paths mapping to empty tsconfig`, async () => { const tree = await schematicRunner.runSchematicAsync('library', defaultOptions, workspaceTree).toPromise(); - const tsConfigJson = getJsonFileContent(tree, 'tsconfig.json'); + const tsConfigJson = getJsonFileContent(tree, 'tsconfig.base.json'); expect(tsConfigJson.compilerOptions.paths.foo).toBeTruthy(); expect(tsConfigJson.compilerOptions.paths.foo.length).toEqual(2); expect(tsConfigJson.compilerOptions.paths.foo[0]).toEqual('dist/foo/foo'); @@ -212,7 +213,7 @@ describe('Library Schematic', () => { }); it(`should append to existing paths mappings`, async () => { - workspaceTree.overwrite('tsconfig.json', JSON.stringify({ + workspaceTree.overwrite('tsconfig.base.json', JSON.stringify({ compilerOptions: { paths: { 'unrelated': ['./something/else.ts'], @@ -222,7 +223,7 @@ describe('Library Schematic', () => { })); const tree = await schematicRunner.runSchematicAsync('library', defaultOptions, workspaceTree).toPromise(); - const tsConfigJson = getJsonFileContent(tree, 'tsconfig.json'); + const tsConfigJson = getJsonFileContent(tree, 'tsconfig.base.json'); expect(tsConfigJson.compilerOptions.paths.foo).toBeTruthy(); expect(tsConfigJson.compilerOptions.paths.foo.length).toEqual(3); expect(tsConfigJson.compilerOptions.paths.foo[1]).toEqual('dist/foo/foo'); @@ -235,7 +236,7 @@ describe('Library Schematic', () => { skipTsConfig: true, }, workspaceTree).toPromise(); - const tsConfigJson = getJsonFileContent(tree, 'tsconfig.json'); + const tsConfigJson = getJsonFileContent(tree, 'tsconfig.base.json'); expect(tsConfigJson.compilerOptions.paths).toBeUndefined(); }); }); @@ -264,12 +265,12 @@ describe('Library Schematic', () => { expect(pkgJson.name).toEqual(scopedName); const tsConfigJson = JSON.parse(tree.readContent('/projects/myscope/mylib/tsconfig.spec.json')); - expect(tsConfigJson.extends).toEqual('../../../tsconfig.json'); + expect(tsConfigJson.extends).toEqual('../../../tsconfig.base.json'); const cfg = JSON.parse(tree.readContent('/angular.json')); expect(cfg.projects['@myscope/mylib']).toBeDefined(); - const rootTsCfg = JSON.parse(tree.readContent('/tsconfig.json')); + const rootTsCfg = JSON.parse(tree.readContent('/tsconfig.base.json')); expect(rootTsCfg.compilerOptions.paths['@myscope/mylib']).toEqual(['dist/myscope/mylib/myscope-mylib', 'dist/myscope/mylib']); const karmaConf = getFileContent(tree, '/projects/myscope/mylib/karma.conf.js'); @@ -314,9 +315,9 @@ describe('Library Schematic', () => { expect(buildOpt.tsConfig).toEqual('foo/tsconfig.lib.json'); const appTsConfig = JSON.parse(tree.readContent('/foo/tsconfig.lib.json')); - expect(appTsConfig.extends).toEqual('../tsconfig.json'); + expect(appTsConfig.extends).toEqual('../tsconfig.base.json'); const specTsConfig = JSON.parse(tree.readContent('/foo/tsconfig.spec.json')); - expect(specTsConfig.extends).toEqual('../tsconfig.json'); + expect(specTsConfig.extends).toEqual('../tsconfig.base.json'); }); it(`should add 'production' configuration`, async () => { @@ -326,4 +327,16 @@ describe('Library Schematic', () => { const workspace = JSON.parse(tree.readContent('/angular.json')); expect(workspace.projects.foo.architect.build.configurations.production).toBeDefined(); }); + + it('should add reference in solution style tsconfig', async () => { + const tree = await schematicRunner.runSchematicAsync('library', defaultOptions, workspaceTree) + .toPromise(); + + // tslint:disable-next-line:no-any + const { references } = parseJson(tree.readContent('/tsconfig.json').toString(), JsonParseMode.Loose) as any; + expect(references).toEqual([ + { path: './projects/foo/tsconfig.lib.json' }, + { path: './projects/foo/tsconfig.spec.json' }, + ]); + }); }); diff --git a/packages/schematics/angular/library/schema.json b/packages/schematics/angular/library/schema.json index 9008d8aad44d..427c8ffc0a69 100644 --- a/packages/schematics/angular/library/schema.json +++ b/packages/schematics/angular/library/schema.json @@ -41,7 +41,7 @@ "skipTsConfig": { "type": "boolean", "default": false, - "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." + "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." }, "lintFix": { "type": "boolean", diff --git a/packages/schematics/angular/migrations/migration-collection.json b/packages/schematics/angular/migrations/migration-collection.json index 5de2f2cc9076..370977e7caae 100644 --- a/packages/schematics/angular/migrations/migration-collection.json +++ b/packages/schematics/angular/migrations/migration-collection.json @@ -101,9 +101,14 @@ "description": "Update library projects to use tslib version 2 as a direct dependency." }, "update-workspace-dependencies": { - "version": "10.0.0-beta.7", + "version": "10.0.0-beta.7", "factory": "./update-10/update-dependencies", "description": "Workspace dependencies updates." + }, + "solution-style-tsconfig": { + "version": "10.0.0-beta.7", + "factory": "./update-10/solution-style-tsconfig", + "description": "Adding \"Solution Style\" tsconfig.json. This improves developer experience using editors powered by TypeScript’s language server." } } } diff --git a/packages/schematics/angular/migrations/update-10/solution-style-tsconfig.ts b/packages/schematics/angular/migrations/update-10/solution-style-tsconfig.ts new file mode 100644 index 000000000000..2a67ed0db165 --- /dev/null +++ b/packages/schematics/angular/migrations/update-10/solution-style-tsconfig.ts @@ -0,0 +1,122 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { JsonAstString, JsonParseMode, dirname, join, normalize, parseJsonAst, resolve } from '@angular-devkit/core'; +import { DirEntry, Rule, chain } from '@angular-devkit/schematics'; +import { findPropertyInAstObject } from '../../utility/json-utils'; +import { getWorkspace } from '../../utility/workspace'; + +const SOLUTIONS_TS_CONFIG_HEADER = '// This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s' + + 'language server to improve development experience.\n' + + '// It is not intended to be used to perform a compilation.\n'; + +function* visitExtendedJsonFiles(directory: DirEntry): IterableIterator<[string, JsonAstString]> { + for (const path of directory.subfiles) { + if (!path.endsWith('.json')) { + continue; + } + + const entry = directory.file(path); + if (!entry) { + continue; + } + + const jsonAst = parseJsonAst(entry.content.toString(), JsonParseMode.Loose); + if (jsonAst.kind !== 'object') { + continue; + } + + const extendsAst = findPropertyInAstObject(jsonAst, 'extends'); + // Check if this config has the potential of extended the workspace tsconfig. + // Unlike tslint configuration, tsconfig "extends" cannot be an array. + if (extendsAst?.kind === 'string' && extendsAst.value.endsWith('tsconfig.json')) { + yield [join(directory.path, path), extendsAst]; + } + } + + for (const path of directory.subdirs) { + if (path === 'node_modules') { + continue; + } + + yield* visitExtendedJsonFiles(directory.dir(path)); + } +} + +function updateTsconfigExtendsRule(): Rule { + return host => { + if (!host.exists('tsconfig.json')) { + return; + } + + // Rename workspace tsconfig to base tsconfig. + host.rename('tsconfig.json', 'tsconfig.base.json'); + + // Iterate over all tsconfig files and change the extends from 'tsconfig.json' 'tsconfig.base.json' + for (const [tsconfigPath, extendsAst] of visitExtendedJsonFiles(host.root)) { + const tsConfigDir = dirname(normalize(tsconfigPath)); + if ('/tsconfig.json' !== resolve(tsConfigDir, normalize(extendsAst.value))) { + // tsconfig extends doesn't refer to the workspace tsconfig path. + continue; + } + + // Replace last path, json -> base.json + const recorder = host.beginUpdate(tsconfigPath); + const offset = extendsAst.end.offset - 5; + recorder.remove(offset, 4); + recorder.insertLeft(offset, 'base.json'); + host.commitUpdate(recorder); + } + }; +} + +function addSolutionTsConfigRule(): Rule { + return async host => { + const tsConfigPaths = new Set(); + const workspace = await getWorkspace(host); + + // Find all tsconfig which are refereces used by builders + for (const [, project] of workspace.projects) { + for (const [, target] of project.targets) { + if (!target.options) { + continue; + } + + for (const [key, value] of Object.entries(target.options)) { + if ((key === 'tsConfig' || key === 'webWorkerTsConfig') && typeof value === 'string') { + tsConfigPaths.add(value); + } + } + } + } + + // Generate the solutions style tsconfig/ + const tsConfigContent = { + files: [], + references: [...tsConfigPaths].map(p => ({ path: `./${p}` })), + }; + + host.create('tsconfig.json', SOLUTIONS_TS_CONFIG_HEADER + JSON.stringify(tsConfigContent, undefined, 2)); + }; +} + +export default function (): Rule { + return (host, context) => { + const logger = context.logger; + + if (host.exists('tsconfig.base.json')) { + logger.info('Migration has already been executed.'); + + return; + } + + return chain([ + updateTsconfigExtendsRule, + addSolutionTsConfigRule, + ]); + }; +} diff --git a/packages/schematics/angular/migrations/update-10/solution-style-tsconfig_spec.ts b/packages/schematics/angular/migrations/update-10/solution-style-tsconfig_spec.ts new file mode 100644 index 000000000000..63ea89c4f809 --- /dev/null +++ b/packages/schematics/angular/migrations/update-10/solution-style-tsconfig_spec.ts @@ -0,0 +1,117 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { JsonParseMode, parseJson } from '@angular-devkit/core'; +import { EmptyTree } from '@angular-devkit/schematics'; +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; +import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; + +describe('Migration to create "Solution Style" tsconfig', () => { + const schematicName = 'solution-style-tsconfig'; + + const schematicRunner = new SchematicTestRunner( + 'migrations', + require.resolve('../migration-collection.json'), + ); + + function createJsonFile(tree: UnitTestTree, filePath: string, content: {}) { + tree.create(filePath, JSON.stringify(content, undefined, 2)); + } + + // tslint:disable-next-line: no-any + function readJsonFile(tree: UnitTestTree, filePath: string): any { + // tslint:disable-next-line: no-any + return parseJson(tree.readContent(filePath).toString(), JsonParseMode.Loose) as any; + } + + function createWorkSpaceConfig(tree: UnitTestTree) { + const angularConfig: WorkspaceSchema = { + version: 1, + projects: { + app: { + root: '', + sourceRoot: 'src', + projectType: ProjectType.Application, + prefix: 'app', + architect: { + build: { + builder: Builders.Browser, + options: { + tsConfig: 'src/tsconfig.app.json', + webWorkerTsConfig: 'src/tsconfig.worker.json', + main: '', + polyfills: '', + }, + }, + test: { + builder: Builders.Karma, + options: { + karmaConfig: '', + tsConfig: 'src/tsconfig.spec.json', + }, + }, + }, + }, + }, + }; + + createJsonFile(tree, 'angular.json', angularConfig); + } + + + let tree: UnitTestTree; + beforeEach(() => { + tree = new UnitTestTree(new EmptyTree()); + createWorkSpaceConfig(tree); + + // Create tsconfigs + const compilerOptions = { target: 'es2015' }; + createJsonFile(tree, 'tsconfig.json', { compilerOptions }); + createJsonFile(tree, 'tsconfig.common.json', { compilerOptions }); + createJsonFile(tree, 'src/tsconfig.json', { extends: './../tsconfig.json', compilerOptions }); + createJsonFile(tree, 'src/tsconfig.tsc.json', { extends: './tsconfig.json', compilerOptions }); + createJsonFile(tree, 'src/tsconfig.app.json', { extends: './../tsconfig.common.json', compilerOptions }); + createJsonFile(tree, 'src/tsconfig.spec.json', { extends: './../tsconfig.json', compilerOptions }); + createJsonFile(tree, 'src/tsconfig.worker.json', { extends: './../tsconfig.json', compilerOptions }); + }); + + it(`should rename 'tsconfig.json' to 'tsconfig.base.json'`, async () => { + const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); + expect(newTree.exists('tsconfig.base.json')).toBeTrue(); + }); + + it(`should update extends from 'tsconfig.json' to 'tsconfig.base.json'`, async () => { + const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); + expect(readJsonFile(newTree, 'src/tsconfig.json').extends).toEqual('./../tsconfig.base.json'); + expect(readJsonFile(newTree, 'src/tsconfig.spec.json').extends).toEqual('./../tsconfig.base.json'); + expect(readJsonFile(newTree, 'src/tsconfig.worker.json').extends).toEqual('./../tsconfig.base.json'); + }); + + it('should not update extends if not extended the root tsconfig', async () => { + const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); + expect(readJsonFile(newTree, 'src/tsconfig.tsc.json').extends).toEqual('./tsconfig.json'); + }); + + it('should add project referenced to root level tsconfig', async () => { + const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); + + expect(readJsonFile(newTree, 'tsconfig.json')).toEqual({ + files: [], + references: [ + { + path: './src/tsconfig.app.json', + }, + { + path: './src/tsconfig.worker.json', + }, + { + path: './src/tsconfig.spec.json', + }, + ], + }); + }); +}); diff --git a/packages/schematics/angular/migrations/update-9/update-app-tsconfigs_spec.ts b/packages/schematics/angular/migrations/update-9/update-app-tsconfigs_spec.ts index cd27a12af0af..f29ca118f16e 100644 --- a/packages/schematics/angular/migrations/update-9/update-app-tsconfigs_spec.ts +++ b/packages/schematics/angular/migrations/update-9/update-app-tsconfigs_spec.ts @@ -55,6 +55,9 @@ describe('Migration to version 9', () => { tree, ) .toPromise(); + + // Pre version 9 - tsconfig.json was the base tsconfig file. + tree.overwrite('tsconfig.json', tree.readContent('tsconfig.base.json')); }); it('should update apps tsConfig with stricter files inclusions', async () => { diff --git a/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts index 982ca0c81ac1..defd2d76a5f4 100644 --- a/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts +++ b/packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts @@ -8,7 +8,6 @@ import { EmptyTree } from '@angular-devkit/schematics'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { latestVersions } from '../../utility/latest-versions'; import { WorkspaceTargets } from '../../utility/workspace-models'; import { ANY_COMPONENT_STYLE_BUDGET } from './update-workspace-config'; @@ -81,6 +80,19 @@ describe('Migration to version 9', () => { tree, ) .toPromise(); + + // Pre version 9 - tsconfig.json was the base tsconfig file. + tree.overwrite('tsconfig.json', tree.readContent('tsconfig.base.json')); + + const tsConfig = JSON.stringify( + { + extends: './tsconfig.json', + }, + null, + 2, + ); + + tree.overwrite('tsconfig.app.json', tsConfig); }); describe('scripts and style options', () => { diff --git a/packages/schematics/angular/universal/files/root/__tsconfigFileName__.json.template b/packages/schematics/angular/universal/files/root/tsconfig.server.json.template similarity index 83% rename from packages/schematics/angular/universal/files/root/__tsconfigFileName__.json.template rename to packages/schematics/angular/universal/files/root/tsconfig.server.json.template index 8c588ac885c8..7331e95d8ff2 100644 --- a/packages/schematics/angular/universal/files/root/__tsconfigFileName__.json.template +++ b/packages/schematics/angular/universal/files/root/tsconfig.server.json.template @@ -1,7 +1,7 @@ { "extends": "./<%= tsConfigExtends %>", "compilerOptions": { - "outDir": "<%= outDir %>-server", + "outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/server", "types": [ "node" ] diff --git a/packages/schematics/angular/universal/index.ts b/packages/schematics/angular/universal/index.ts index 31c762d1b4f7..0d40359fb953 100644 --- a/packages/schematics/angular/universal/index.ts +++ b/packages/schematics/angular/universal/index.ts @@ -6,12 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ import { - JsonParseMode, Path, basename, join, normalize, - parseJson, strings, } from '@angular-devkit/core'; import { @@ -34,7 +32,9 @@ import { findNode, getDecoratorMetadata } from '../utility/ast-utils'; import { InsertChange } from '../utility/change'; import { addPackageJsonDependency, getPackageJsonDependency } from '../utility/dependencies'; import { findBootstrapModuleCall, findBootstrapModulePath } from '../utility/ng-ast-utils'; +import { relativePathToWorkspaceRoot } from '../utility/paths'; import { targetBuildNotFoundError } from '../utility/project-targets'; +import { addTsConfigProjectReferences, verifyBaseTsConfigExists } from '../utility/tsconfig'; import { getWorkspace, updateWorkspace } from '../utility/workspace'; import { BrowserBuilderOptions, Builders, OutputHashing } from '../utility/workspace-models'; import { Schema as UniversalOptions } from './schema'; @@ -68,13 +68,15 @@ function updateConfigFile(options: UniversalOptions, tsConfigDirectory: Path): R } const mainPath = options.main as string; + const serverTsConfig = join(tsConfigDirectory, 'tsconfig.server.json'); + clientProject.targets.add({ name: 'server', builder: Builders.Server, options: { outputPath: `dist/${options.clientProject}/server`, main: join(normalize(clientProject.root), 'src', mainPath.endsWith('.ts') ? mainPath : mainPath + '.ts'), - tsConfig: join(tsConfigDirectory, `${options.tsconfigFileName}.json`), + tsConfig: serverTsConfig, }, configurations: { production: { @@ -85,6 +87,12 @@ function updateConfigFile(options: UniversalOptions, tsConfigDirectory: Path): R }, }, }); + + const lintTarget = clientProject.targets.get('lint'); + if (lintTarget && lintTarget.options && Array.isArray(lintTarget.options.tsConfig)) { + lintTarget.options.tsConfig = + lintTarget.options.tsConfig.concat(serverTsConfig); + } } }); } @@ -218,23 +226,6 @@ function addDependencies(): Rule { }; } -function getTsConfigOutDir(host: Tree, tsConfigPath: string): string { - const tsConfigBuffer = host.read(tsConfigPath); - if (!tsConfigBuffer) { - throw new SchematicsException(`Could not read ${tsConfigPath}`); - } - const tsConfigContent = tsConfigBuffer.toString(); - const tsConfig = parseJson(tsConfigContent, JsonParseMode.Loose); - if (tsConfig === null || typeof tsConfig !== 'object' || Array.isArray(tsConfig) || - tsConfig.compilerOptions === null || typeof tsConfig.compilerOptions !== 'object' || - Array.isArray(tsConfig.compilerOptions)) { - throw new SchematicsException(`Invalid tsconfig - ${tsConfigPath}`); - } - const outDir = tsConfig.compilerOptions.outDir; - - return outDir as string; -} - export default function (options: UniversalOptions): Rule { return async (host: Tree, context: SchematicContext) => { const workspace = await getWorkspace(host); @@ -248,11 +239,12 @@ export default function (options: UniversalOptions): Rule { if (!clientBuildTarget) { throw targetBuildNotFoundError(); } + + verifyBaseTsConfigExists(host); + const clientBuildOptions = (clientBuildTarget.options || {}) as unknown as BrowserBuilderOptions; - const outDir = getTsConfigOutDir(host, clientBuildOptions.tsConfig); - const clientTsConfig = normalize(clientBuildOptions.tsConfig); const tsConfigExtends = basename(clientTsConfig); // this is needed because prior to version 8, tsconfig might have been in 'src' @@ -279,8 +271,8 @@ export default function (options: UniversalOptions): Rule { ...strings, ...options as object, stripTsExtension: (s: string) => s.replace(/\.ts$/, ''), - outDir, tsConfigExtends, + relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(tsConfigDirectory), rootInSrc, }), move(tsConfigDirectory), @@ -293,6 +285,9 @@ export default function (options: UniversalOptions): Rule { updateConfigFile(options, tsConfigDirectory), wrapBootstrapCall(clientBuildOptions.main), addServerTransition(options, clientBuildOptions.main, clientProject.root), + addTsConfigProjectReferences([ + join(tsConfigDirectory, 'tsconfig.server.json'), + ]), ]); }; } diff --git a/packages/schematics/angular/universal/index_spec.ts b/packages/schematics/angular/universal/index_spec.ts index 291c48da379b..f94c4a7e6133 100644 --- a/packages/schematics/angular/universal/index_spec.ts +++ b/packages/schematics/angular/universal/index_spec.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import { JsonParseMode, parseJson } from '@angular-devkit/core'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { Schema as ApplicationOptions, Style } from '../application/schema'; import { NodeDependencyType, addPackageJsonDependency } from '../utility/dependencies'; @@ -90,7 +91,7 @@ describe('Universal Schematic', () => { expect(JSON.parse(contents)).toEqual({ extends: './tsconfig.app.json', compilerOptions: { - outDir: './out-tsc/app-server', + outDir: './out-tsc/server', types: ['node'], }, files: [ @@ -114,7 +115,7 @@ describe('Universal Schematic', () => { expect(JSON.parse(contents)).toEqual({ extends: './tsconfig.app.json', compilerOptions: { - outDir: '../../out-tsc/app-server', + outDir: '../../out-tsc/server', types: ['node'], }, files: [ @@ -258,4 +259,21 @@ describe('Universal Schematic', () => { const contents = tree.readContent(filePath); expect(contents).toContain('@angular/localize/init'); }); + + it('should add reference in solution style tsconfig', async () => { + const tree = await schematicRunner.runSchematicAsync('universal', workspaceUniversalOptions, appTree) + .toPromise(); + + // tslint:disable-next-line:no-any + const { references } = parseJson(tree.readContent('/tsconfig.json').toString(), JsonParseMode.Loose) as any; + expect(references).toEqual([ + { path: './tsconfig.app.json' }, + { path: './tsconfig.spec.json' }, + { path: './e2e/tsconfig.json' }, + { path: './projects/bar/tsconfig.app.json' }, + { path: './projects/bar/tsconfig.spec.json' }, + { path: './projects/bar/e2e/tsconfig.json' }, + { path: './tsconfig.server.json' }, + ]); + }); }); diff --git a/packages/schematics/angular/universal/schema.json b/packages/schematics/angular/universal/schema.json index 8026bff4ec78..cced43f602c0 100644 --- a/packages/schematics/angular/universal/schema.json +++ b/packages/schematics/angular/universal/schema.json @@ -24,7 +24,8 @@ "tsconfigFileName": { "type": "string", "default": "tsconfig.server", - "description": "The name of the TypeScript configuration file." + "description": "The name of the TypeScript configuration file.", + "x-deprecated": "This option has no effect." }, "appDir": { "type": "string", diff --git a/packages/schematics/angular/utility/tsconfig.ts b/packages/schematics/angular/utility/tsconfig.ts new file mode 100644 index 000000000000..4d6679a1d863 --- /dev/null +++ b/packages/schematics/angular/utility/tsconfig.ts @@ -0,0 +1,70 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { JsonParseMode, parseJsonAst } from '@angular-devkit/core'; +import { Rule, SchematicsException, Tree } from '@angular-devkit/schematics'; +import { appendValueInAstArray, findPropertyInAstObject } from './json-utils'; + +const SOLUTION_TSCONFIG_PATH = 'tsconfig.json'; + +/** + * Add project references in "Solution Style" tsconfig. + */ +export function addTsConfigProjectReferences(paths: string[]): Rule { + return (host, context) => { + const logger = context.logger; + + // We need to read after each write to avoid missing `,` when appending multiple items. + for (const path of paths) { + const source = host.read(SOLUTION_TSCONFIG_PATH); + if (!source) { + // Solution tsconfig doesn't exist. + logger.warn(`Cannot add reference '${path}' in '${SOLUTION_TSCONFIG_PATH}'. File doesn't exists.`); + + return; + } + + const jsonAst = parseJsonAst(source.toString(), JsonParseMode.Loose); + if (jsonAst?.kind !== 'object') { + // Invalid JSON + throw new SchematicsException(`Invalid JSON AST Object '${SOLUTION_TSCONFIG_PATH}'.`); + } + + // Solutions style tsconfig can contain 2 properties: + // - 'files' with a value of empty array + // - 'references' + const filesAst = findPropertyInAstObject(jsonAst, 'files'); + const referencesAst = findPropertyInAstObject(jsonAst, 'references'); + if ( + filesAst?.kind !== 'array' || + filesAst.elements.length !== 0 || + referencesAst?.kind !== 'array' + ) { + logger.warn(`Cannot add reference '${path}' in '${SOLUTION_TSCONFIG_PATH}'. It appears to be an invalid solution style tsconfig.`); + + return; + } + + // Append new paths + const recorder = host.beginUpdate(SOLUTION_TSCONFIG_PATH); + appendValueInAstArray(recorder, referencesAst, { 'path': `./${path}` }, 4); + host.commitUpdate(recorder); + } + }; +} + +/** + * Throws an exception when the base tsconfig doesn't exists. + */ +export function verifyBaseTsConfigExists(host: Tree): void { + if (host.exists('tsconfig.base.json')) { + return; + } + + throw new SchematicsException(`Cannot find base TypeScript configuration file 'tsconfig.base.json'.`); +} diff --git a/packages/schematics/angular/utility/tsconfig_spec.ts b/packages/schematics/angular/utility/tsconfig_spec.ts new file mode 100644 index 000000000000..9f9a0f3516ba --- /dev/null +++ b/packages/schematics/angular/utility/tsconfig_spec.ts @@ -0,0 +1,101 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { EmptyTree, SchematicContext, Tree, callRule } from '@angular-devkit/schematics'; +import { addTsConfigProjectReferences } from './tsconfig'; + +const SOLUTION_TSCONFIG_PATH = 'tsconfig.json'; + +describe('addTsConfigProjectReference', () => { + const context = { + // tslint:disable-next-line:no-any + logger: {} as any, + } as SchematicContext; + + const createTsConfig = (content: object) => + tree.create(SOLUTION_TSCONFIG_PATH, JSON.stringify(content, undefined, 2)); + + const parseTsConfig = (tree: Tree) => + // tslint:disable-next-line:no-non-null-assertion + JSON.parse(tree.read(SOLUTION_TSCONFIG_PATH)!.toString()); + + let tree: Tree; + beforeEach(() => { + tree = new EmptyTree(); + }); + + it('works when references is an empty array', async () => { + createTsConfig({ + files: [], + references: [], + }); + + const result = await callRule( + addTsConfigProjectReferences(['foo/tsconfig.app.json']), + tree, + context, + ) + .toPromise(); + + // tslint:disable-next-line:no-non-null-assertion + const { references } = parseTsConfig(result); + expect(references).toEqual([ + { path: './foo/tsconfig.app.json' }, + ]); + }); + + it('works when references contains an element', async () => { + createTsConfig({ + files: [], + references: [ + { path: './foo/tsconfig.spec.json' }, + ], + }); + + const result = await callRule( + addTsConfigProjectReferences(['foo/tsconfig.app.json']), + tree, + context, + ) + .toPromise(); + + // tslint:disable-next-line:no-non-null-assertion + const { references } = parseTsConfig(result); + expect(references).toEqual([ + { path: './foo/tsconfig.spec.json' }, + { path: './foo/tsconfig.app.json' }, + ]); + }); + + it('works when adding multiple references and contains an element', async () => { + createTsConfig({ + files: [], + references: [ + { path: './foo/tsconfig.spec.json' }, + ], + }); + + const result = await callRule( + addTsConfigProjectReferences([ + 'foo/tsconfig.app.json', + 'foo/tsconfig.server.json', + ]), + tree, + context, + ) + .toPromise(); + + // tslint:disable-next-line:no-non-null-assertion + const { references } = parseTsConfig(result); + expect(references).toEqual([ + { path: './foo/tsconfig.spec.json' }, + { path: './foo/tsconfig.app.json' }, + { path: './foo/tsconfig.server.json' }, + ]); + }); +}); diff --git a/packages/schematics/angular/web-worker/files/worker-tsconfig/tsconfig.worker.json.template b/packages/schematics/angular/web-worker/files/worker-tsconfig/tsconfig.worker.json.template index 166d26bac399..881a7b6e1e52 100644 --- a/packages/schematics/angular/web-worker/files/worker-tsconfig/tsconfig.worker.json.template +++ b/packages/schematics/angular/web-worker/files/worker-tsconfig/tsconfig.worker.json.template @@ -1,5 +1,5 @@ { - "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.json", + "extends": "<%= relativePathToWorkspaceRoot %>/tsconfig.base.json", "compilerOptions": { "outDir": "<%= relativePathToWorkspaceRoot %>/out-tsc/worker", "lib": [ diff --git a/packages/schematics/angular/web-worker/index.ts b/packages/schematics/angular/web-worker/index.ts index 2e430ce5121e..77d83ac73f11 100644 --- a/packages/schematics/angular/web-worker/index.ts +++ b/packages/schematics/angular/web-worker/index.ts @@ -22,6 +22,7 @@ import { import { appendValueInAstArray, findPropertyInAstObject } from '../utility/json-utils'; import { parseName } from '../utility/parse-name'; import { relativePathToWorkspaceRoot } from '../utility/paths'; +import { addTsConfigProjectReferences, verifyBaseTsConfigExists } from '../utility/tsconfig'; import { buildDefaultPath, getWorkspace, updateWorkspace } from '../utility/workspace'; import { BrowserBuilderOptions, LintBuilderOptions } from '../utility/workspace-models'; import { Schema as WebWorkerOptions } from './schema'; @@ -121,7 +122,6 @@ export default function (options: WebWorkerOptions): Rule { if (!options.target) { throw new SchematicsException('Option "target" is required.'); } - const project = workspace.projects.get(options.project); if (!project) { throw new SchematicsException(`Invalid project name (${options.project})`); @@ -131,6 +131,8 @@ export default function (options: WebWorkerOptions): Rule { throw new SchematicsException(`Web Worker requires a project type of "application".`); } + verifyBaseTsConfigExists(host); + const projectTarget = project.targets.get(options.target); if (!projectTarget) { throw new Error(`Target is not defined for this project.`); @@ -171,6 +173,9 @@ export default function (options: WebWorkerOptions): Rule { options.snippet ? addSnippet(options) : noop(), // Add the worker. mergeWith(templateSource), + addTsConfigProjectReferences([ + `${root}/tsconfig.worker.json`, + ]), ]); }; } diff --git a/packages/schematics/angular/web-worker/index_spec.ts b/packages/schematics/angular/web-worker/index_spec.ts index 6e4409029f15..5eb12d4ff657 100644 --- a/packages/schematics/angular/web-worker/index_spec.ts +++ b/packages/schematics/angular/web-worker/index_spec.ts @@ -5,12 +5,12 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import { JsonParseMode, parseJson } from '@angular-devkit/core'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { Schema as ApplicationOptions } from '../application/schema'; import { Schema as WorkspaceOptions } from '../workspace/schema'; import { Schema as WebWorkerOptions } from './schema'; - describe('Web Worker Schematic', () => { const schematicRunner = new SchematicTestRunner( '@schematics/angular', @@ -134,7 +134,7 @@ describe('Web Worker Schematic', () => { appTree.overwrite('/angular.json', JSON.stringify(workspace)); const oldTsConfig = { - extends: '../../../tsconfig.json', + extends: '../../../tsconfig.base.json', include: [ '**/*.ts', ], @@ -150,4 +150,18 @@ describe('Web Worker Schematic', () => { const { exclude } = JSON.parse(tree.readContent(tsConfigPath)); expect(exclude).toContain('**/*.worker.ts'); }); + + it('should add reference in solution style tsconfig', async () => { + const tree = await schematicRunner.runSchematicAsync('web-worker', defaultOptions, appTree) + .toPromise(); + + // tslint:disable-next-line:no-any + const { references } = parseJson(tree.readContent('/tsconfig.json').toString(), JsonParseMode.Loose) as any; + expect(references).toEqual([ + { path: './projects/bar/tsconfig.app.json' }, + { path: './projects/bar/tsconfig.spec.json' }, + { path: './projects/bar/e2e/tsconfig.json' }, + { path: './projects/bar/tsconfig.worker.json' }, + ]); + }); }); diff --git a/packages/schematics/angular/workspace/files/tsconfig.base.json.template b/packages/schematics/angular/workspace/files/tsconfig.base.json.template new file mode 100644 index 000000000000..6a70101e15b2 --- /dev/null +++ b/packages/schematics/angular/workspace/files/tsconfig.base.json.template @@ -0,0 +1,27 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc",<% if (strict) { %> + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true,<% } %> + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "es2015", + "module": "es2020", + "lib": [ + "es2018", + "dom" + ] + }<% if (strict) { %>, + "angularCompilerOptions": { + "strictInjectionParameters": true, + "strictTemplates": true + }<% } %> +} diff --git a/packages/schematics/angular/workspace/files/tsconfig.json.template b/packages/schematics/angular/workspace/files/tsconfig.json.template index c92051232587..7a82ed695098 100644 --- a/packages/schematics/angular/workspace/files/tsconfig.json.template +++ b/packages/schematics/angular/workspace/files/tsconfig.json.template @@ -1,27 +1,6 @@ +// This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. +// It is not intended to be used to perform a compilation. { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc",<% if (strict) { %> - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true,<% } %> - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "module": "es2020", - "moduleResolution": "node", - "importHelpers": true, - "target": "es2015", - "lib": [ - "es2018", - "dom" - ] - }<% if (strict) { %>, - "angularCompilerOptions": { - "strictInjectionParameters": true, - "strictTemplates": true - }<% } %> + "files": [], + "references": [] } diff --git a/packages/schematics/angular/workspace/index_spec.ts b/packages/schematics/angular/workspace/index_spec.ts index 1d722a8334b5..2f6d3b222113 100644 --- a/packages/schematics/angular/workspace/index_spec.ts +++ b/packages/schematics/angular/workspace/index_spec.ts @@ -32,6 +32,7 @@ describe('Workspace Schematic', () => { '/package.json', '/README.md', '/tsconfig.json', + '/tsconfig.base.json', '/tslint.json', ])); }); @@ -66,6 +67,7 @@ describe('Workspace Schematic', () => { '/package.json', '/README.md', '/tsconfig.json', + '/tsconfig.base.json', ])); expect(files).not.toContain('/tslint.json'); @@ -74,14 +76,14 @@ describe('Workspace Schematic', () => { it('should not add strict compiler options when false', async () => { const tree = await schematicRunner.runSchematicAsync('workspace', { ...defaultOptions, strict: false }).toPromise(); - const { compilerOptions, angularCompilerOptions } = JSON.parse(tree.readContent('/tsconfig.json')); + const { compilerOptions, angularCompilerOptions } = JSON.parse(tree.readContent('/tsconfig.base.json')); expect(compilerOptions.strict).toBeUndefined(); expect(angularCompilerOptions).toBeUndefined(); }); it('should not add strict compiler options when true', async () => { const tree = await schematicRunner.runSchematicAsync('workspace', { ...defaultOptions, strict: true }).toPromise(); - const { compilerOptions, angularCompilerOptions } = JSON.parse(tree.readContent('/tsconfig.json')); + const { compilerOptions, angularCompilerOptions } = JSON.parse(tree.readContent('/tsconfig.base.json')); expect(compilerOptions.strict).toBe(true); expect(angularCompilerOptions.strictTemplates).toBe(true); }); diff --git a/tests/legacy-cli/e2e/setup/500-create-project.ts b/tests/legacy-cli/e2e/setup/500-create-project.ts index c166b8e122de..0bb253a36ee5 100644 --- a/tests/legacy-cli/e2e/setup/500-create-project.ts +++ b/tests/legacy-cli/e2e/setup/500-create-project.ts @@ -23,7 +23,7 @@ export default async function() { process.chdir('./test-project'); if (argv['ve']) { - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { const { angularCompilerOptions = {} } = config; angularCompilerOptions.enableIvy = false; config.angularCompilerOptions = angularCompilerOptions; diff --git a/tests/legacy-cli/e2e/tests/basic/ivy-opt-out.ts b/tests/legacy-cli/e2e/tests/basic/ivy-opt-out.ts index 5c6e0c913dc9..71f87a3bb4db 100644 --- a/tests/legacy-cli/e2e/tests/basic/ivy-opt-out.ts +++ b/tests/legacy-cli/e2e/tests/basic/ivy-opt-out.ts @@ -21,7 +21,7 @@ export default async function() { await ng('e2e', '--prod'); // View Engine (NGC) compilation should work after running NGCC from Webpack - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { const { angularCompilerOptions = {} } = config; angularCompilerOptions.enableIvy = false; config.angularCompilerOptions = angularCompilerOptions; diff --git a/tests/legacy-cli/e2e/tests/build/worker.ts b/tests/legacy-cli/e2e/tests/build/worker.ts index e5e9c1a8f495..48c7f4dbbe66 100644 --- a/tests/legacy-cli/e2e/tests/build/worker.ts +++ b/tests/legacy-cli/e2e/tests/build/worker.ts @@ -13,7 +13,7 @@ import { expectToFail } from '../../utils/utils'; export default async function () { const workerPath = join('src', 'app', 'app.worker.ts'); const snippetPath = join('src', 'app', 'app.component.ts'); - const projectTsConfig = 'tsconfig.json'; + const projectTsConfig = 'tsconfig.base.json'; const workerTsConfig = 'tsconfig.worker.json'; // Enable Differential loading to run both size checks diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts index e7f49abff057..518fdcb1475c 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts @@ -20,7 +20,7 @@ export async function executeTest() { 'IE 9-11', ); - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { config.compilerOptions.target = 'es2015'; if (!config.angularCompilerOptions) { config.angularCompilerOptions = {}; diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts index fcb41e30d8b5..45f0785b94a9 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts @@ -10,7 +10,7 @@ export default async function() { // Ensure a ES2015 build is used. await writeFile('.browserslistrc', 'Chrome 65'); - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { config.compilerOptions.target = 'es2015'; if (!config.angularCompilerOptions) { config.angularCompilerOptions = {}; diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts index 223100b20292..8a9fcac4f328 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts @@ -9,7 +9,7 @@ export default async function() { await setupI18nConfig(); // Ensure a es5 build is used. - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { config.compilerOptions.target = 'es5'; if (!config.angularCompilerOptions) { config.angularCompilerOptions = {}; diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-serviceworker.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-serviceworker.ts index 242b878054fe..ec10a863c9bf 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-serviceworker.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-serviceworker.ts @@ -32,7 +32,7 @@ export default async function() { } await npm('install', `${serviceWorkerVersion}`); - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { config.compilerOptions.target = 'es2015'; if (!config.angularCompilerOptions) { config.angularCompilerOptions = {}; diff --git a/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts b/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts index 19d5a45778ed..fe6e6a7758d7 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ve-localize-es2015.ts @@ -23,7 +23,7 @@ export default async function() { // Ensure a ES2015 build is used. await writeFile('.browserslistrc', 'Chrome 65'); - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { config.compilerOptions.target = 'es2015'; if (!config.angularCompilerOptions) { config.angularCompilerOptions = {}; diff --git a/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts b/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts index 3a7e6ccb90a8..21ac1949c472 100644 --- a/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts +++ b/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts @@ -4,7 +4,7 @@ import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; export default async function () { - await updateJsonFile('tsconfig.json', configJson => { + await updateJsonFile('tsconfig.base.json', configJson => { const compilerOptions = configJson['compilerOptions']; compilerOptions['target'] = 'es5'; }); diff --git a/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts b/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts index 497f60aab160..6faeba5f2b1e 100644 --- a/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts +++ b/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts @@ -36,7 +36,7 @@ export default async function() { // Turn on emitDecoratorMetadata await replaceInFile( - 'tsconfig.json', + 'tsconfig.base.json', '"experimentalDecorators": true', '"experimentalDecorators": true, "emitDecoratorMetadata": true', ); diff --git a/tests/legacy-cli/e2e/tests/misc/module-resolution.ts b/tests/legacy-cli/e2e/tests/misc/module-resolution.ts index 0396650258c7..7acba1b81eaa 100644 --- a/tests/legacy-cli/e2e/tests/misc/module-resolution.ts +++ b/tests/legacy-cli/e2e/tests/misc/module-resolution.ts @@ -5,7 +5,7 @@ import { expectToFail } from '../../utils/utils'; export default async function () { - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = { '*': ['./node_modules/*'], }; @@ -20,14 +20,14 @@ export default async function () { await expectToFail(() => ng('build')); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = { '@angular/common': [ './xyz/common' ], }; }); await ng('build'); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = { '*': ['./node_modules/*'], '@angular/common': [ './xyz/common' ], @@ -35,7 +35,7 @@ export default async function () { }); await ng('build'); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = { '@angular/common': [ './xyz/common' ], '*': ['./node_modules/*'], @@ -43,7 +43,7 @@ export default async function () { }); await ng('build'); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { delete tsconfig.compilerOptions.paths; }); @@ -58,12 +58,12 @@ export default async function () { await ng('build', '--aot'); await ng('test', '--watch=false'); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = {}; }); await ng('build'); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = { '@app/*': ['*'], '@lib/*/test': ['*/test'], @@ -71,14 +71,14 @@ export default async function () { }); await ng('build'); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = { '@firebase/polyfill': ['./node_modules/@firebase/polyfill/index.ts'], }; }); await expectToFail(() => ng('build')); - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.paths = { '@firebase/polyfill*': ['./node_modules/@firebase/polyfill/index.ts'], }; diff --git a/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts b/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts index 30c8113ce9ec..68b3fef7bec4 100644 --- a/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts +++ b/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts @@ -2,7 +2,7 @@ import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; export default async function () { - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.traceResolution = true; }); @@ -11,7 +11,7 @@ export default async function () { throw new Error(`Modules resolutions must be printed when 'traceResolution' is enabled.`); } - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.base.json', tsconfig => { tsconfig.compilerOptions.traceResolution = false; }); diff --git a/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts b/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts index f00c1087589e..6b14d6ec1105 100644 --- a/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts +++ b/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts @@ -28,7 +28,7 @@ export default async function() { await writeMultipleFiles({ './tsconfig.server.json': ` { - "extends": "./tsconfig.json", + "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "../dist-server", "baseUrl": "./", diff --git a/tests/legacy-cli/e2e/tests/test/test-target.ts b/tests/legacy-cli/e2e/tests/test/test-target.ts index ba1afd13d1c2..c9f79c64cff9 100644 --- a/tests/legacy-cli/e2e/tests/test/test-target.ts +++ b/tests/legacy-cli/e2e/tests/test/test-target.ts @@ -6,7 +6,7 @@ export default function () { // TypeError: Assignment to constant variable. return; - return updateJsonFile('tsconfig.json', configJson => { + return updateJsonFile('tsconfig.base.json', configJson => { const compilerOptions = configJson['compilerOptions']; compilerOptions['target'] = 'es2015'; }) diff --git a/tests/legacy-cli/e2e/tests/update/update-7.0.ts b/tests/legacy-cli/e2e/tests/update/update-7.0.ts index 6f24b6fd61f1..df394cb881ea 100644 --- a/tests/legacy-cli/e2e/tests/update/update-7.0.ts +++ b/tests/legacy-cli/e2e/tests/update/update-7.0.ts @@ -28,8 +28,7 @@ export default async function() { 'src/app/app-routing.module.ts', `loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)`, ); - // Should update tsconfig and src/browserslist via differential-loading. - await expectFileToMatch('tsconfig.json', `"target": "es2015",`); + await expectToFail(() => expectFileToExist('e2e/browserlist')); // Should rename codelyzer rules. await expectFileToMatch('tslint.json', `use-lifecycle-interface`); diff --git a/tests/legacy-cli/e2e/utils/project.ts b/tests/legacy-cli/e2e/utils/project.ts index 397ae7d5f860..7efcba61e8ad 100644 --- a/tests/legacy-cli/e2e/utils/project.ts +++ b/tests/legacy-cli/e2e/utils/project.ts @@ -6,7 +6,7 @@ import { prependToFile, readFile, replaceInFile, writeFile } from './fs'; import { gitCommit } from './git'; import { execAndWaitForOutputToMatch, git, ng, npm, silentNpm } from './process'; -const tsConfigPath = 'tsconfig.json'; +const tsConfigPath = 'tsconfig.base.json'; export function updateJsonFile(filePath: string, fn: (json: any) => any | void) { @@ -39,10 +39,10 @@ export async function createProject(name: string, ...args: string[]) { await ng('new', name, '--skip-install', ...extraArgs, ...args); process.chdir(name); - if (fs.existsSync('tsconfig.json')) { + if (fs.existsSync('tsconfig.base.json')) { // Disable the TS version check to make TS updates easier. // Only VE does it, but on Ivy the i18n extraction uses VE. - await updateJsonFile('tsconfig.json', config => { + await updateJsonFile('tsconfig.base.json', config => { if (!config.angularCompilerOptions) { config.angularCompilerOptions = {}; } @@ -101,7 +101,7 @@ export async function prepareProjectForE2e(name) { ); // Force sourcemaps to be from the root of the filesystem. await updateJsonFile( - 'tsconfig.json', + 'tsconfig.base.json', json => { json[ 'compilerOptions'