From 69e6c717fd28a71ee771803ee8dfd14a01ed02ff Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Wed, 22 Feb 2017 15:08:45 +0000 Subject: [PATCH] feat(@angular/cli): use separate tsconfigs This PR adds tsconfigs for each separate application: - `src/tsconfig.app.json`: configuration for the Angular app. - `src/tsconfig.spec.json`: configuration for the unit tests. Defaults to the Angular app config. - `e2e/tsconfig.e2e.json`: configuration for the e2e tests. There is an additional root-level `tsconfig.json` that is used for editor integration. For Angular version 4 projects, these tsconfigs will use inheritance since it's available with TypeScript 2.1. This is not a breaking change. Existing projects should not be affected. --- docs/documentation/stories/third-party-lib.md | 12 +++ .../ng2/files/__path__/tsconfig.app.json | 28 ++++++ .../ng2/files/__path__/tsconfig.json | 21 ----- .../ng2/files/__path__/tsconfig.spec.json | 28 ++++++ .../cli/blueprints/ng2/files/angular-cli.json | 12 +-- .../e2e/{tsconfig.json => tsconfig.e2e.json} | 18 ++-- .../blueprints/ng2/files/protractor.conf.js | 2 +- .../cli/blueprints/ng2/files/tsconfig.json | 14 +++ packages/@angular/cli/lib/config/schema.json | 6 +- .../@angular/cli/models/webpack-config.ts | 1 + .../cli/models/webpack-configs/typescript.ts | 86 ++++++++----------- tests/e2e/tests/build/aot/exclude.ts | 29 +++++++ tests/e2e/tests/misc/different-file-format.ts | 2 +- tests/e2e/utils/project.ts | 2 +- 14 files changed, 171 insertions(+), 90 deletions(-) create mode 100644 packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.app.json delete mode 100644 packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.json create mode 100644 packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.spec.json rename packages/@angular/cli/blueprints/ng2/files/e2e/{tsconfig.json => tsconfig.e2e.json} (67%) create mode 100644 packages/@angular/cli/blueprints/ng2/files/tsconfig.json create mode 100644 tests/e2e/tests/build/aot/exclude.ts diff --git a/docs/documentation/stories/third-party-lib.md b/docs/documentation/stories/third-party-lib.md index 7c3e8e2c931f..272635865808 100644 --- a/docs/documentation/stories/third-party-lib.md +++ b/docs/documentation/stories/third-party-lib.md @@ -9,6 +9,18 @@ npm install d3 --save npm install @types/d3 --save-dev ``` +Then open `src/tsconfig.app.json` and add it to the `types` array: + +``` +"types":[ + "d3" +] +``` + +If the library you added typings for is only to be used on your e2e tests, +instead use `e2e/tsconfig.e2e.json`. +The same goes for unit tests and `src/tsconfig.spec.json`. + If the library doesn't have typings available at `@types/`, you can still use it by manually adding typings for it: diff --git a/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.app.json b/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.app.json new file mode 100644 index 000000000000..493162c0438c --- /dev/null +++ b/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.app.json @@ -0,0 +1,28 @@ +{<% if (ng4) { %> + "extends": "<%= relativeRootPath %>/tsconfig.json", + "compilerOptions": { + "lib": [ + "es2016", + "dom" + ],<% } else { %> + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016", + "dom" + ],<% } %> + "outDir": "<%= relativeRootPath %>/out-tsc/app", + "target": "es5", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.json b/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.json deleted file mode 100644 index ccc9fc420ccc..000000000000 --- a/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": "", - "declaration": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": [ - "es2016", - "dom" - ], - "mapRoot": "./", - "module": "es2015", - "moduleResolution": "node", - "outDir": "<%= relativeRootPath %>/dist/out-tsc", - "sourceMap": true, - "target": "es5", - "typeRoots": [ - "<%= relativeRootPath %>/node_modules/@types" - ] - } -} diff --git a/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.spec.json b/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.spec.json new file mode 100644 index 000000000000..dfbeeb249854 --- /dev/null +++ b/packages/@angular/cli/blueprints/ng2/files/__path__/tsconfig.spec.json @@ -0,0 +1,28 @@ +{<% if (ng4) { %> + "extends": "<%= relativeRootPath %>/tsconfig.json", + "compilerOptions": {<% } else { %> + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016" + ],<% } %> + "outDir": "<%= relativeRootPath %>/out-tsc/spec", + "module": "commonjs", + "target": "es6", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts" + ] +} diff --git a/packages/@angular/cli/blueprints/ng2/files/angular-cli.json b/packages/@angular/cli/blueprints/ng2/files/angular-cli.json index 8a57d08bac51..93a828e33062 100644 --- a/packages/@angular/cli/blueprints/ng2/files/angular-cli.json +++ b/packages/@angular/cli/blueprints/ng2/files/angular-cli.json @@ -15,7 +15,8 @@ "main": "main.ts", "polyfills": "polyfills.ts", "test": "test.ts", - "tsconfig": "tsconfig.json", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", "prefix": "<%= prefix %>", "styles": [ "styles.<%= styleExt %>" @@ -35,12 +36,13 @@ }, "lint": [ { - "files": "<%= sourceDir %>/**/*.ts", - "project": "<%= sourceDir %>/tsconfig.json" + "project": "<%= sourceDir %>/tsconfig.app.json" }, { - "files": "e2e/**/*.ts", - "project": "e2e/tsconfig.json" + "project": "<%= sourceDir %>/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" } ], "test": { diff --git a/packages/@angular/cli/blueprints/ng2/files/e2e/tsconfig.json b/packages/@angular/cli/blueprints/ng2/files/e2e/tsconfig.e2e.json similarity index 67% rename from packages/@angular/cli/blueprints/ng2/files/e2e/tsconfig.json rename to packages/@angular/cli/blueprints/ng2/files/e2e/tsconfig.e2e.json index 94da47a24286..7697b8b04eb4 100644 --- a/packages/@angular/cli/blueprints/ng2/files/e2e/tsconfig.json +++ b/packages/@angular/cli/blueprints/ng2/files/e2e/tsconfig.e2e.json @@ -1,19 +1,21 @@ -{ - "compileOnSave": false, +{<% if (ng4) { %> + "extends": "../tsconfig.json", + "compilerOptions": {<% } else { %> "compilerOptions": { + "sourceMap": true, "declaration": false, + "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": [ "es2016" - ], - "module": "commonjs", - "moduleResolution": "node", + ],<% } %> "outDir": "../dist/out-tsc-e2e", - "sourceMap": true, + "module": "commonjs", "target": "es6", - "typeRoots": [ - "../node_modules/@types" + "types":[ + "jasmine", + "node" ] } } diff --git a/packages/@angular/cli/blueprints/ng2/files/protractor.conf.js b/packages/@angular/cli/blueprints/ng2/files/protractor.conf.js index c819669c0a01..2cbc3293913f 100644 --- a/packages/@angular/cli/blueprints/ng2/files/protractor.conf.js +++ b/packages/@angular/cli/blueprints/ng2/files/protractor.conf.js @@ -22,7 +22,7 @@ exports.config = { }, beforeLaunch: function() { require('ts-node').register({ - project: 'e2e' + project: 'e2e/tsconfig.e2e.json' }); }, onPrepare() { diff --git a/packages/@angular/cli/blueprints/ng2/files/tsconfig.json b/packages/@angular/cli/blueprints/ng2/files/tsconfig.json new file mode 100644 index 000000000000..cde5e35054a2 --- /dev/null +++ b/packages/@angular/cli/blueprints/ng2/files/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016" + ] + } +} diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json index 2d7ec3128257..ad62cf40243d 100644 --- a/packages/@angular/cli/lib/config/schema.json +++ b/packages/@angular/cli/lib/config/schema.json @@ -94,9 +94,13 @@ }, "tsconfig": { "type": "string", - "default": "tsconfig.json", + "default": "tsconfig.app.json", "description": "The name of the TypeScript configuration file." }, + "testTsconfig": { + "type": "string", + "description": "The name of the TypeScript configuration file for unit tests." + }, "prefix": { "type": "string", "description": "The prefix to apply to generated selectors." diff --git a/packages/@angular/cli/models/webpack-config.ts b/packages/@angular/cli/models/webpack-config.ts index 4c5654d19b88..4af56c7563b0 100644 --- a/packages/@angular/cli/models/webpack-config.ts +++ b/packages/@angular/cli/models/webpack-config.ts @@ -106,6 +106,7 @@ export class NgCliWebpackConfig { public addAppConfigDefaults(appConfig: any) { const appConfigDefaults: any = { + testTsconfig: appConfig.tsconfig, scripts: [], styles: [] }; diff --git a/packages/@angular/cli/models/webpack-configs/typescript.ts b/packages/@angular/cli/models/webpack-configs/typescript.ts index 7d1afa63a74a..8ddc57383a2b 100644 --- a/packages/@angular/cli/models/webpack-configs/typescript.ts +++ b/packages/@angular/cli/models/webpack-configs/typescript.ts @@ -64,79 +64,61 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) { } return new AotPlugin(Object.assign({}, { - tsConfigPath: path.resolve(projectRoot, appConfig.root, appConfig.tsconfig), mainPath: path.join(projectRoot, appConfig.root, appConfig.main), i18nFile: buildOptions.i18nFile, i18nFormat: buildOptions.i18nFormat, locale: buildOptions.locale, - hostReplacementPaths + hostReplacementPaths, + // If we don't explicitely list excludes, it will default to `['**/*.spec.ts']`. + exclude: [] }, options)); } export const getNonAotConfig = function(wco: WebpackConfigOptions) { - const { projectRoot, appConfig } = wco; - let exclude = [ '**/*.spec.ts' ]; - if (appConfig.test) { - exclude.push(path.join(projectRoot, appConfig.root, appConfig.test)); - } + const { appConfig, projectRoot } = wco; + const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig); return { - module: { - rules: [ - { - test: /\.ts$/, - loader: webpackLoader, - exclude: [/\.(spec|e2e)\.ts$/] - } - ] - }, - plugins: [ - _createAotPlugin(wco, { exclude, skipCodeGeneration: true }), - ] + module: { rules: [{ test: /\.ts$/, loader: webpackLoader }] }, + plugins: [ _createAotPlugin(wco, { tsConfigPath, skipCodeGeneration: true }) ] }; }; export const getAotConfig = function(wco: WebpackConfigOptions) { const { projectRoot, appConfig } = wco; - let exclude = [ '**/*.spec.ts' ]; - if (appConfig.test) { exclude.push(path.join(projectRoot, appConfig.root, appConfig.test)); }; + const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig); + const testTsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.testTsconfig); + + let pluginOptions: any = { tsConfigPath }; + + // Fallback to exclude spec files from AoT compilation on projects using a shared tsconfig. + if (testTsConfigPath === tsConfigPath) { + let exclude = [ '**/*.spec.ts' ]; + if (appConfig.test) { exclude.push(path.join(projectRoot, appConfig.root, appConfig.test)); }; + pluginOptions.exclude = exclude; + } + return { - module: { - rules: [ - { - test: /\.ts$/, - loader: webpackLoader, - exclude: [/\.(spec|e2e)\.ts$/] - } - ] - }, - plugins: [ - _createAotPlugin(wco, { exclude }) - ] + module: { rules: [{ test: /\.ts$/, loader: webpackLoader }] }, + plugins: [ _createAotPlugin(wco, pluginOptions) ] }; }; export const getNonAotTestConfig = function(wco: WebpackConfigOptions) { + const { projectRoot, appConfig } = wco; + const tsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.testTsconfig); + const appTsConfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig); + + let pluginOptions: any = { tsConfigPath, skipCodeGeneration: true }; + + // Fallback to correct module format on projects using a shared tsconfig. + if (tsConfigPath === appTsConfigPath) { + pluginOptions.compilerOptions = { module: 'commonjs' }; + } + return { - module: { - rules: [ - { - test: /\.ts$/, - loader: webpackLoader, - query: { module: 'commonjs' }, - exclude: [/\.(e2e)\.ts$/] - } - ] - }, - plugins: [ - _createAotPlugin(wco, { - exclude: [], - skipCodeGeneration: true, - compilerOptions: { - module: 'commonjs' - } - }), - ] + module: { rules: [{ test: /\.ts$/, loader: webpackLoader }] }, + plugins: [ _createAotPlugin(wco, pluginOptions) ] }; }; diff --git a/tests/e2e/tests/build/aot/exclude.ts b/tests/e2e/tests/build/aot/exclude.ts new file mode 100644 index 000000000000..6f1518e9302c --- /dev/null +++ b/tests/e2e/tests/build/aot/exclude.ts @@ -0,0 +1,29 @@ +import { ng } from '../../../utils/process'; +import { writeFile, moveFile } from '../../../utils/fs'; +import { updateJsonFile } from '../../../utils/project'; +import { getGlobalVariable } from '../../../utils/env'; + +export default function () { + // Disable parts of it in webpack tests. + const ejected = getGlobalVariable('argv').eject; + + // Check if **/*.spec.ts files are excluded by default. + return Promise.resolve() + // This import would cause aot to fail. + .then(() => writeFile('src/another.component.spec.ts', ` + import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; + `)) + .then(() => ng('build', '--aot')) + // Verify backwards compatibility with old project using the shared tsconfig. + .then(() => moveFile('src/tsconfig.app.json', 'src/tsconfig.json')) + .then(() => updateJsonFile('.angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app.tsconfig = 'tsconfig.json'; + delete app['testTsconfig']; + })) + .then(() => updateJsonFile('src/tsconfig.json', tsconfigJson => { + delete tsconfigJson['exclude']; + })) + .then(() => ng('build', '--aot')) + .then(() => !ejected && ng('test', '--single-run')); +} diff --git a/tests/e2e/tests/misc/different-file-format.ts b/tests/e2e/tests/misc/different-file-format.ts index e6dad9a7a069..9f1abbb7eab5 100644 --- a/tests/e2e/tests/misc/different-file-format.ts +++ b/tests/e2e/tests/misc/different-file-format.ts @@ -9,7 +9,7 @@ const options = { export default function() { return Promise.resolve() - .then(() => fs.prependToFile('./src/tsconfig.json', '\ufeff', options)) + .then(() => fs.prependToFile('./src/tsconfig.app.json', '\ufeff', options)) .then(() => fs.prependToFile('./.angular-cli.json', '\ufeff', options)) .then(() => ng('build', '--env=dev')); } diff --git a/tests/e2e/utils/project.ts b/tests/e2e/utils/project.ts index f89dd7726968..07e6acd9a73b 100644 --- a/tests/e2e/utils/project.ts +++ b/tests/e2e/utils/project.ts @@ -5,7 +5,7 @@ import {getGlobalVariable} from './env'; const packages = require('../../../lib/packages'); -const tsConfigPath = 'src/tsconfig.json'; +const tsConfigPath = 'src/tsconfig.app.json'; export function updateJsonFile(filePath: string, fn: (json: any) => any | void) {