diff --git a/aio/content/guide/aot-compiler.md b/aio/content/guide/aot-compiler.md index 83295a7f18..8a46e61d92 100644 --- a/aio/content/guide/aot-compiler.md +++ b/aio/content/guide/aot-compiler.md @@ -1308,6 +1308,28 @@ Chuck: After reviewing your PR comment I'm still at a loss. See [comment there]( } ``` +{@a tsconfig-extends} +## Configuration inheritance with extends +Similar to TypeScript Compiler, Angular Compiler also supports `extends` in the `tsconfig.json` on `angularCompilerOptions`. A tsconfig file can inherit configurations from another file using the `extends` property. + The `extends` is a top level property parallel to `compilerOptions` and `angularCompilerOptions`. + The configuration from the base file are loaded first, then overridden by those in the inheriting config file. + Example: +```json +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "experimentalDecorators": true, + ... + }, + "angularCompilerOptions": { + "fullTemplateTypeCheck": true, + "preserveWhitespaces": true, + ... + } +} +``` + More information about tsconfig extends can be found in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). + {@a compiler-options} ## Angular template compiler options diff --git a/packages/compiler-cli/src/perform_compile.ts b/packages/compiler-cli/src/perform_compile.ts index 9335dc525b..e62950cdc4 100644 --- a/packages/compiler-cli/src/perform_compile.ts +++ b/packages/compiler-cli/src/perform_compile.ts @@ -128,7 +128,37 @@ export function readConfiguration( try { const {projectFile, basePath} = calcProjectFileAndBasePath(project); - let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile); + const readExtendedConfigFile = + (configFile: string, existingConfig?: any): {config?: any, error?: ts.Diagnostic} => { + const {config, error} = ts.readConfigFile(configFile, ts.sys.readFile); + + if (error) { + return {error}; + } + + // we are only interested into merging 'angularCompilerOptions' as + // other options like 'compilerOptions' are merged by TS + const baseConfig = existingConfig || config; + if (existingConfig) { + baseConfig.angularCompilerOptions = {...config.angularCompilerOptions, + ...baseConfig.angularCompilerOptions}; + } + + if (config.extends) { + let extendedConfigPath = path.resolve(path.dirname(configFile), config.extends); + extendedConfigPath = path.extname(extendedConfigPath) ? extendedConfigPath : + `${extendedConfigPath}.json`; + + if (fs.existsSync(extendedConfigPath)) { + // Call read config recursively as TypeScript only merges CompilerOptions + return readExtendedConfigFile(extendedConfigPath, baseConfig); + } + } + + return {config: baseConfig}; + }; + + const {config, error} = readExtendedConfigFile(projectFile); if (error) { return { diff --git a/packages/compiler-cli/test/BUILD.bazel b/packages/compiler-cli/test/BUILD.bazel index 75cacf05ed..8ace3a7195 100644 --- a/packages/compiler-cli/test/BUILD.bazel +++ b/packages/compiler-cli/test/BUILD.bazel @@ -133,3 +133,30 @@ jasmine_node_test( "//tools/testing:node", ], ) + +# perform_compile_spec +ts_library( + name = "perform_compile_lib", + testonly = 1, + srcs = [ + "perform_compile_spec.ts", + ], + deps = [ + ":test_utils", + "//packages/compiler", + "//packages/compiler-cli", + ], +) + +jasmine_node_test( + name = "perform_compile", + bootstrap = ["angular/tools/testing/init_node_spec.js"], + data = [ + "//packages/core:npm_package", + ], + deps = [ + ":perform_compile_lib", + "//packages/core", + "//tools/testing:node", + ], +) diff --git a/packages/compiler-cli/test/perform_compile_spec.ts b/packages/compiler-cli/test/perform_compile_spec.ts new file mode 100644 index 0000000000..9c838783e0 --- /dev/null +++ b/packages/compiler-cli/test/perform_compile_spec.ts @@ -0,0 +1,58 @@ +/** + * @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 * as path from 'path'; + +import {readConfiguration} from '../src/perform_compile'; + +import {TestSupport, setup} from './test_support'; + +describe('perform_compile', () => { + let support: TestSupport; + let basePath: string; + + beforeEach(() => { + support = setup(); + basePath = support.basePath; + }); + + function writeSomeConfigs() { + support.writeFiles({ + 'tsconfig-level-1.json': `{ + "extends": "./tsconfig-level-2.json", + "angularCompilerOptions": { + "annotateForClosureCompiler": true + } + } + `, + 'tsconfig-level-2.json': `{ + "extends": "./tsconfig-level-3.json", + "angularCompilerOptions": { + "skipMetadataEmit": true + } + } + `, + 'tsconfig-level-3.json': `{ + "angularCompilerOptions": { + "annotateForClosureCompiler": false, + "annotationsAs": "decorators" + } + } + `, + }); + } + + it('should merge tsconfig "angularCompilerOptions"', () => { + writeSomeConfigs(); + const {options} = readConfiguration(path.resolve(basePath, 'tsconfig-level-1.json')); + expect(options.annotateForClosureCompiler).toBe(true); + expect(options.annotationsAs).toBe('decorators'); + expect(options.skipMetadataEmit).toBe(true); + }); + +});