diff --git a/.travis.yml b/.travis.yml index 038d74b90c0a..9b50b6185d4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,6 +43,24 @@ matrix: os: linux script: node tests/run_e2e.js --nb-shards=4 --shard=3 --nosilent env: e2e-3 + + - node_js: "6" + os: linux + script: node tests/run_e2e.js --nb-shards=4 --shard=0 --nosilent --nightly + env: nightly-0 + - node_js: "6" + os: linux + script: node tests/run_e2e.js --nb-shards=4 --shard=1 --nosilent --nightly + env: nightly-1 + - node_js: "6" + os: linux + script: node tests/run_e2e.js --nb-shards=4 --shard=2 --nosilent --nightly + env: nightly-2 + - node_js: "6" + os: linux + script: node tests/run_e2e.js --nb-shards=4 --shard=3 --nosilent --nightly + env: nightly-3 + - node_js: "6" os: linux script: node tests/run_e2e.js --eject "--glob=tests/build/**" @@ -54,11 +72,6 @@ matrix: before_script: if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then exit 0; fi script: node tests/run_e2e.js --ng2 "--glob=tests/{build,test,misc}/**" env: ng2 - - node_js: "6" - os: linux - before_script: if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then exit 0; fi - script: node tests/run_e2e.js "--nightly --glob=tests/{build,test,misc}/**" - env: nightly - node_js: "7" os: linux before_script: if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then exit 0; fi diff --git a/lib/bootstrap-local.js b/lib/bootstrap-local.js index 87c26379d360..977eccade98d 100644 --- a/lib/bootstrap-local.js +++ b/lib/bootstrap-local.js @@ -46,6 +46,14 @@ require.extensions['.ts'] = function (m, filename) { // lazy: true // }); +const resolve = require('resolve'); + + +// Look if there's a .angular-cli.json file, and if so toggle process.cwd() resolution. +const isAngularProject = fs.existsSync(path.join(process.cwd(), '.angular-cli.json')) + || fs.existsSync(path.join(process.cwd(), 'angular-cli.json')); + + // If we're running locally, meaning npm linked. This is basically "developer mode". if (!__dirname.match(new RegExp(`\\${path.sep}node_modules\\${path.sep}`))) { const packages = require('./packages').packages; @@ -71,6 +79,14 @@ if (!__dirname.match(new RegExp(`\\${path.sep}node_modules\\${path.sep}`))) { const p = path.join(packages[match].root, request.substr(match.length)); return oldLoad.call(this, p, parent); } else { + try { + if (isAngularProject) { + return oldLoad.call(this, resolve.sync(request, { basedir: process.cwd() }), parent); + } + } catch (e) { + // Do nothing. Fallback to the old method. + } + return oldLoad.apply(this, arguments); } } diff --git a/packages/@angular/cli/models/webpack-configs/typescript.ts b/packages/@angular/cli/models/webpack-configs/typescript.ts index 780640b12352..82f5b43e25ef 100644 --- a/packages/@angular/cli/models/webpack-configs/typescript.ts +++ b/packages/@angular/cli/models/webpack-configs/typescript.ts @@ -134,7 +134,9 @@ export function getAotConfig(wco: WebpackConfigOptions) { // 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)); } + if (appConfig.test) { + exclude.push(path.join(projectRoot, appConfig.root, appConfig.test)); + } pluginOptions.exclude = exclude; } @@ -164,7 +166,10 @@ export function getNonAotTestConfig(wco: WebpackConfigOptions) { // Force include main and polyfills. // This is needed for AngularCompilerPlugin compatibility with existing projects, // since TS compilation there is stricter and tsconfig.spec.ts doesn't include them. - const include = [appConfig.main, appConfig.polyfills]; + const include = [appConfig.main, appConfig.polyfills, '**/*.spec.ts']; + if (appConfig.test) { + include.push(appConfig.test); + } let pluginOptions: any = { tsConfigPath, skipCodeGeneration: true, include }; diff --git a/packages/@ngtools/webpack/src/angular_compiler_plugin.ts b/packages/@ngtools/webpack/src/angular_compiler_plugin.ts index 748c963ea30b..40d65fbd0d3d 100644 --- a/packages/@ngtools/webpack/src/angular_compiler_plugin.ts +++ b/packages/@ngtools/webpack/src/angular_compiler_plugin.ts @@ -41,10 +41,11 @@ import { CompilerHost, Diagnostics, CustomTransformers, + EmitFlags, + LazyRoute, createProgram, createCompilerHost, formatDiagnostics, - EmitFlags, } from './ngtools_api'; import { findAstNodes } from './transformers/ast_helpers'; @@ -87,7 +88,7 @@ export class AngularCompilerPlugin implements Tapable { private _compilerOptions: ts.CompilerOptions; private _angularCompilerOptions: CompilerOptions; private _tsFilenames: string[]; - private _program: ts.Program | Program; + private _program: (ts.Program | Program); private _compilerHost: WebpackCompilerHost; private _moduleResolutionCache: ts.ModuleResolutionCache; private _angularCompilerHost: WebpackCompilerHost & CompilerHost; @@ -111,6 +112,14 @@ export class AngularCompilerPlugin implements Tapable { private _forkTypeChecker = true; private _typeCheckerProcess: ChildProcess; + private get _ngCompilerSupportsNewApi() { + if (this._JitMode) { + return false; + } else { + return !!(this._program as Program).listLazyRoutes; + } + } + constructor(options: AngularCompilerPluginOptions) { CompilerCliIsSupported(); this._options = Object.assign({}, options); @@ -364,8 +373,10 @@ export class AngularCompilerPlugin implements Tapable { timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ng.createProgram'); time('AngularCompilerPlugin._createOrUpdateProgram.ng.loadNgStructureAsync'); - return this._program.loadNgStructureAsync().then(() => - timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ng.loadNgStructureAsync')); + return this._program.loadNgStructureAsync() + .then(() => { + timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ng.loadNgStructureAsync'); + }); } } @@ -409,6 +420,32 @@ export class AngularCompilerPlugin implements Tapable { return result; } + private _listLazyRoutesFromProgram(): LazyRouteMap { + const ngProgram = this._program as Program; + if (!ngProgram.listLazyRoutes) { + throw new Error('_listLazyRoutesFromProgram was called with an old program.'); + } + + const lazyRoutes = ngProgram.listLazyRoutes(); + + return lazyRoutes.reduce( + (acc: LazyRouteMap, curr: LazyRoute) => { + const ref = curr.route; + if (ref in acc && acc[ref] !== curr.referencedModule.filePath) { + throw new Error( + + `Duplicated path in loadChildren detected: "${ref}" is used in 2 loadChildren, ` + + `but they point to different modules "(${acc[ref]} and ` + + `"${curr.referencedModule.filePath}"). Webpack cannot distinguish on context and ` + + 'would fail to load the proper one.' + ); + } + acc[ref] = curr.referencedModule.filePath; + return acc; + }, + {} as LazyRouteMap + ); + } + // Process the lazy routes discovered, adding then to _lazyRoutes. // TODO: find a way to remove lazy routes that don't exist anymore. // This will require a registry of known references to a lazy route, removing it when no @@ -666,6 +703,10 @@ export class AngularCompilerPlugin implements Tapable { return Promise.resolve() .then(() => { + if (this._ngCompilerSupportsNewApi) { + return; + } + // Try to find lazy routes. // We need to run the `listLazyRoutes` the first time because it also navigates libraries // and other things that we might miss using the (faster) findLazyRoutesInAst. @@ -684,6 +725,12 @@ export class AngularCompilerPlugin implements Tapable { return this._createOrUpdateProgram(); } }) + .then(() => { + if (this._ngCompilerSupportsNewApi) { + // TODO: keep this when the new ngCompiler supports the new lazy routes API. + this._lazyRoutes = this._listLazyRoutesFromProgram(); + } + }) .then(() => { // Build transforms, emit and report errors if there are changes or it's the first run. if (changedFiles.length > 0 || this._firstRun) { diff --git a/packages/@ngtools/webpack/src/ngtools_api.ts b/packages/@ngtools/webpack/src/ngtools_api.ts index 84300962336e..d9d133c22133 100644 --- a/packages/@ngtools/webpack/src/ngtools_api.ts +++ b/packages/@ngtools/webpack/src/ngtools_api.ts @@ -76,6 +76,7 @@ export interface Program { getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): Diagnostic[]; loadNgStructureAsync(): Promise; + listLazyRoutes?(): LazyRoute[]; emit({ emitFlags, cancellationToken, customTransformers, emitCallback }: { emitFlags?: any; cancellationToken?: ts.CancellationToken; @@ -84,6 +85,12 @@ export interface Program { }): ts.EmitResult; } +export interface LazyRoute { + route: string; + module: { name: string, filePath: string }; + referencedModule: { name: string, filePath: string }; +} + export declare type Diagnostics = Array; // Interfaces for the function declarations. diff --git a/packages/@ngtools/webpack/src/transformers/export_lazy_module_map.ts b/packages/@ngtools/webpack/src/transformers/export_lazy_module_map.ts index e28dcf835f2c..40019cbc0339 100644 --- a/packages/@ngtools/webpack/src/transformers/export_lazy_module_map.ts +++ b/packages/@ngtools/webpack/src/transformers/export_lazy_module_map.ts @@ -20,7 +20,7 @@ export function exportLazyModuleMap( let [, moduleName] = loadChildrenString.split('#'); let modulePath = lazyRoutes[loadChildrenString]; - if (modulePath.endsWith('.ngfactory.ts')) { + if (modulePath.match(/\.ngfactory\.[jt]s$/)) { modulePath = modulePath.replace('.ngfactory', ''); moduleName = moduleName.replace('NgFactory', ''); loadChildrenString = loadChildrenString