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/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..e76c5bffeb9d 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,16 @@ 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. + 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 = {}; // process environment file replacement @@ -76,12 +85,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 +96,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 +113,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..2e5ff05eeeb4 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -113,35 +113,28 @@ 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']; + } + + // 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); - 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/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')); +}