From 0176828ecaefe736f460729f13323105df753ef2 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 21 Feb 2017 11:16:36 +0000 Subject: [PATCH 1/2] feat(@angular/cli): expose test include list The `karma` entry in `.angular-cli.json` now supports an `include` property, listing the test specific files to be included in the TypeScript compilation: ``` "test": { "karma": { "config": "./karma.conf.js" }, "include": [ "**/*.spec.ts", "test.ts" ] }, ``` Existing projects will default to the list abose. This list will also be used as an exclude list for AoT Compilations. Fix #3973 --- packages/@angular/cli/lib/config/schema.json | 11 +++++++ .../cli/models/webpack-configs/typescript.ts | 20 +++++------ packages/@ngtools/webpack/src/plugin.ts | 33 ++++--------------- tests/e2e/tests/build/aot/aot.ts | 10 ++++-- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json index c3c3ad42cef6..1f529b1f52e4 100644 --- a/packages/@angular/cli/lib/config/schema.json +++ b/packages/@angular/cli/lib/config/schema.json @@ -262,6 +262,17 @@ } }, "additionalProperties": false + }, + "include":{ + "description": "Test files to include in the TypeScript compilation.", + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "**/*.spec.ts", + "test.ts" + ] } }, "additionalProperties": false diff --git a/packages/@angular/cli/models/webpack-configs/typescript.ts b/packages/@angular/cli/models/webpack-configs/typescript.ts index b3fac77f34ca..ae801cfbf6be 100644 --- a/packages/@angular/cli/models/webpack-configs/typescript.ts +++ b/packages/@angular/cli/models/webpack-configs/typescript.ts @@ -2,6 +2,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { stripIndent } from 'common-tags'; import {AotPlugin, AotPluginOptions} from '@ngtools/webpack'; + +import { CliConfig } from '../config'; import { WebpackConfigOptions } from '../webpack-config'; const SilentError = require('silent-error'); @@ -13,9 +15,12 @@ const webpackLoader: string = g['angularCliIsLocal'] : '@ngtools/webpack'; -function _createAotPlugin(wco: WebpackConfigOptions, options: any) { +function _createAotPlugin(wco: WebpackConfigOptions, options: any = {}) { const { appConfig, projectRoot, buildOptions } = wco; + // Exclude test files by default. + options.exclude = CliConfig.fromProject().config.test.include; + // Read the environment, and set it in the compiler host. let hostReplacementPaths: any = {}; // process environment file replacement @@ -76,12 +81,6 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) { 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)); - } - return { module: { rules: [ @@ -93,15 +92,12 @@ export const getNonAotConfig = function(wco: WebpackConfigOptions) { ] }, plugins: [ - _createAotPlugin(wco, { exclude, skipCodeGeneration: true }), + _createAotPlugin(wco, { 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)); }; return { module: { rules: [ @@ -113,7 +109,7 @@ export const getAotConfig = function(wco: WebpackConfigOptions) { ] }, plugins: [ - _createAotPlugin(wco, { exclude }) + _createAotPlugin(wco) ] }; }; diff --git a/packages/@ngtools/webpack/src/plugin.ts b/packages/@ngtools/webpack/src/plugin.ts index c6e824fcb08b..0d75b19561b1 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -113,35 +113,16 @@ export class AotPlugin implements Tapable { } catch (err) { throw new Error(`An error happened while parsing ${this._tsConfigPath} JSON: ${err}.`); } + + // Default excludes to **/*.spec.ts files. + if (!options.hasOwnProperty('exclude')) { + options.exclude = ['**/*.spec.ts']; + } + tsConfigJson.exclude = (tsConfigJson.exclude || []).concat(options.exclude); + const tsConfig = ts.parseJsonConfigFileContent( tsConfigJson, ts.sys, basePath, null, this._tsConfigPath); - let fileNames = tsConfig.fileNames; - if (options.hasOwnProperty('exclude')) { - let exclude: string[] = typeof options.exclude == 'string' - ? [options.exclude as string] : (options.exclude as string[]); - - exclude.forEach((pattern: string) => { - const basePathPattern = '(' + basePath.replace(/\\/g, '/') - .replace(/[\-\[\]\/{}()+?.\\^$|*]/g, '\\$&') + ')?'; - pattern = pattern - // Replace windows path separators with forward slashes. - .replace(/\\/g, '/') - // Escape characters that are used normally in regexes, except stars. - .replace(/[\-\[\]{}()+?.\\^$|]/g, '\\$&') - // Two stars replacement. - .replace(/\*\*/g, '(?:.*)') - // One star replacement. - .replace(/\*/g, '(?:[^/]*)') - // Escape characters from the basePath and make sure it's forward slashes. - .replace(/^/, basePathPattern); - - const re = new RegExp('^' + pattern + '$'); - fileNames = fileNames.filter(x => !x.replace(/\\/g, '/').match(re)); - }); - } else { - fileNames = fileNames.filter(fileName => !/\.spec\.ts$/.test(fileName)); - } this._rootFilePath = fileNames; // Check the genDir. We generate a default gendir that's under basepath; it will generate diff --git a/tests/e2e/tests/build/aot/aot.ts b/tests/e2e/tests/build/aot/aot.ts index d10cca8bf7a3..ef5ce1bd10b4 100644 --- a/tests/e2e/tests/build/aot/aot.ts +++ b/tests/e2e/tests/build/aot/aot.ts @@ -1,8 +1,14 @@ import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import {expectFileToMatch, writeFile, createDir} from '../../../utils/fs'; export default function() { return ng('build', '--aot') .then(() => expectFileToMatch('dist/main.bundle.js', - /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//)); + /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//)) + // Check if **/*.spec.ts files are excluded by default. This import will cause aot to fail. + .then(() => createDir('./src/much/deep/folder')) + .then(() => writeFile('./src/much/deep/folder/unit-test.spec.ts', ` + import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; + `)) + .then(() => ng('build', '--aot')); } From a0c67423b8e6934ff1eccc3a43884e60b09a35c3 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 21 Feb 2017 11:50:05 +0000 Subject: [PATCH 2/2] bugfixes --- .../cli/blueprints/ng2/files/angular-cli.json | 6 +++++- .../cli/models/webpack-configs/typescript.ts | 6 +++++- packages/@ngtools/webpack/src/plugin.ts | 16 ++++++++++++++-- tests/e2e/tests/build/aot/aot.ts | 10 ++-------- tests/e2e/tests/build/aot/exclude.ts | 17 +++++++++++++++++ 5 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 tests/e2e/tests/build/aot/exclude.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..5cf039859ee6 100644 --- a/packages/@angular/cli/blueprints/ng2/files/angular-cli.json +++ b/packages/@angular/cli/blueprints/ng2/files/angular-cli.json @@ -46,7 +46,11 @@ "test": { "karma": { "config": "./karma.conf.js" - } + }, + "include": [ + "**/*.spec.ts", + "test.ts" + ] }, "defaults": { "styleExt": "<%= styleExt %>", diff --git a/packages/@angular/cli/models/webpack-configs/typescript.ts b/packages/@angular/cli/models/webpack-configs/typescript.ts index ae801cfbf6be..e76c5bffeb9d 100644 --- a/packages/@angular/cli/models/webpack-configs/typescript.ts +++ b/packages/@angular/cli/models/webpack-configs/typescript.ts @@ -19,7 +19,11 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any = {}) { const { appConfig, projectRoot, buildOptions } = wco; // Exclude test files by default. - options.exclude = CliConfig.fromProject().config.test.include; + const testConfig = CliConfig.fromProject().config.test; + options.exclude = (testConfig && testConfig.include) || [ + '**/*.spec.ts', + 'test.ts' + ]; // Read the environment, and set it in the compiler host. let hostReplacementPaths: any = {}; diff --git a/packages/@ngtools/webpack/src/plugin.ts b/packages/@ngtools/webpack/src/plugin.ts index 0d75b19561b1..2e5ff05eeeb4 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -116,9 +116,21 @@ export class AotPlugin implements Tapable { // Default excludes to **/*.spec.ts files. if (!options.hasOwnProperty('exclude')) { - options.exclude = ['**/*.spec.ts']; + options['exclude'] = ['**/*.spec.ts']; } - tsConfigJson.exclude = (tsConfigJson.exclude || []).concat(options.exclude); + + // If the tsconfig doesn't contain any excludes, we must add the default ones before adding + // any extra ones (otherwise we'd include all of these which can cause unexpected errors). + // This is the same logic as present in TypeScript. + if (!tsConfigJson.exclude) { + tsConfigJson['exclude'] = ['node_modules', 'bower_components', 'jspm_packages']; + if (tsConfigJson.compilerOptions && tsConfigJson.compilerOptions.outDir) { + tsConfigJson.exclude.push(tsConfigJson.compilerOptions.outDir); + } + } + + // Join our custom excludes with the existing ones. + tsConfigJson.exclude = tsConfigJson.exclude.concat(options.exclude); const tsConfig = ts.parseJsonConfigFileContent( tsConfigJson, ts.sys, basePath, null, this._tsConfigPath); diff --git a/tests/e2e/tests/build/aot/aot.ts b/tests/e2e/tests/build/aot/aot.ts index ef5ce1bd10b4..d10cca8bf7a3 100644 --- a/tests/e2e/tests/build/aot/aot.ts +++ b/tests/e2e/tests/build/aot/aot.ts @@ -1,14 +1,8 @@ import {ng} from '../../../utils/process'; -import {expectFileToMatch, writeFile, createDir} from '../../../utils/fs'; +import {expectFileToMatch} from '../../../utils/fs'; export default function() { return ng('build', '--aot') .then(() => expectFileToMatch('dist/main.bundle.js', - /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//)) - // Check if **/*.spec.ts files are excluded by default. This import will cause aot to fail. - .then(() => createDir('./src/much/deep/folder')) - .then(() => writeFile('./src/much/deep/folder/unit-test.spec.ts', ` - import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; - `)) - .then(() => ng('build', '--aot')); + /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//)); } diff --git a/tests/e2e/tests/build/aot/exclude.ts b/tests/e2e/tests/build/aot/exclude.ts new file mode 100644 index 000000000000..69f16483fcba --- /dev/null +++ b/tests/e2e/tests/build/aot/exclude.ts @@ -0,0 +1,17 @@ +import { ng } from '../../../utils/process'; +import { writeFile } from '../../../utils/fs'; +import { updateJsonFile } from '../../../utils/project'; + +export default function () { + // Check if **/*.spec.ts files are excluded by default. + return Promise.resolve() + .then(() => updateJsonFile('.angular-cli.json', configJson => { + const test = configJson['test']; + delete test['include']; + })) + // This import would cause aot to fail. + .then(() => writeFile('src/app.component.spec.ts', ` + import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; + `)) + .then(() => ng('build', '--aot')); +}