Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.
Browse files Browse the repository at this point in the history
hansl committed Jan 26, 2017
1 parent e69dd03 commit 4f43b82
Showing 6 changed files with 98 additions and 35 deletions.
11 changes: 7 additions & 4 deletions packages/@ngtools/webpack/src/lazy_routes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {dirname, join} from 'path';
import * as ts from 'typescript';

import {TypeScriptFileRefactor} from './refactor';
@@ -34,7 +35,7 @@ export function findLazyRoutes(filePath: string,
// Take all `loadChildren` elements.
.reduce((acc: ts.PropertyAssignment[], props: ts.PropertyAssignment[]) => {
return acc.concat(props.filter(literal => {
return _getContentOfKeyLiteral(refactor.sourceFile, literal) == 'loadChildren';
return _getContentOfKeyLiteral(refactor.sourceFile, literal.name) == 'loadChildren';
}));
}, [])
// Get only string values.
@@ -45,12 +46,14 @@ export function findLazyRoutes(filePath: string,
// does not exist.
.map((routePath: string) => {
const moduleName = routePath.split('#')[0];
const resolvedModuleName = ts.resolveModuleName(
moduleName, filePath, program.getCompilerOptions(), host);
const resolvedModuleName: ts.ResolvedModuleWithFailedLookupLocations = moduleName[0] == '.'
? { resolvedModule: { resolvedFileName: join(dirname(filePath), moduleName) + '.ts' },
failedLookupLocations: [] }
: ts.resolveModuleName(moduleName, filePath, program.getCompilerOptions(), host);
if (resolvedModuleName.resolvedModule
&& resolvedModuleName.resolvedModule.resolvedFileName
&& host.fileExists(resolvedModuleName.resolvedModule.resolvedFileName)) {
return [routePath, resolvedModuleName];
return [routePath, resolvedModuleName.resolvedModule.resolvedFileName];
} else {
return [routePath, null];
}
28 changes: 18 additions & 10 deletions packages/@ngtools/webpack/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ export class AotPlugin implements Tapable {
private _rootFilePath: string[];
private _compilerHost: WebpackCompilerHost;
private _resourceLoader: WebpackResourceLoader;
private _lazyRoutes: { [route: string]: string } = Object.create(null);
private _lazyRoutes: LazyRouteMap = Object.create(null);
private _tsConfigPath: string;
private _entryModule: string;

@@ -201,19 +201,22 @@ export class AotPlugin implements Tapable {
private _findLazyRoutesInAst(): LazyRouteMap {
const result: LazyRouteMap = Object.create(null);
const changedFilePaths = this._compilerHost.getChangedFilePaths();
for (const filePath in changedFilePaths) {
for (const filePath of changedFilePaths) {
const fileLazyRoutes = findLazyRoutes(filePath, this._program, this._compilerHost);
for (const routeKey of Object.keys(fileLazyRoutes)) {
const route = fileLazyRoutes[routeKey];
if (routeKey in this._lazyRoutes) {
if (route === null) {
this._lazyRoutes[routeKey] = null;
} else if (this._lazyRoutes[routeKey] !== route) {
throw new Error(`Duplicated path in loadChildren detected: "${routeKey}" is used in 2 `
+ `loadChildren, but they point to different modules `
+ `("${this._lazyRoutes[routeKey]}" and "${route}"). Webpack cannot `
+ `distinguish on context and would fail to load the proper one.`);
this._compilation.warnings.push(
new Error(`Duplicated path in loadChildren detected during a rebuild. `
+ `We will take the latest version detected and override it to save rebuild time. `
+ `You should perform a full build to validate that your routes don't overlap.`)
);
}
} else {
result[routeKey] = route;
}
}
}
@@ -346,13 +349,12 @@ export class AotPlugin implements Tapable {
.then(() => {
// Populate the file system cache with the virtual module.
this._compilerHost.populateWebpackResolver(this._compiler.resolvers.normal);
this._compilerHost.resetChangedFileTracker();
})
.then(() => {
// We need to run the `listLazyRoutes` the first time because it also navigates libraries
// and other things that we might miss using the findLazyRoutesInAst.
let discoveredLazyRoutes: LazyRouteMap = this.firstRun
? __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
let discoveredLazyRoutes: LazyRouteMap = this.firstRun ?
__NGTOOLS_PRIVATE_API_2.listLazyRoutes({
program: this._program,
host: this._compilerHost,
angularCompilerOptions: this._angularCompilerOptions,
@@ -365,6 +367,10 @@ export class AotPlugin implements Tapable {
.forEach(k => {
const lazyRoute = discoveredLazyRoutes[k];
k = k.split('#')[0];
if (lazyRoute === null) {
return;
}

if (this.skipCodeGeneration) {
this._lazyRoutes[k] = lazyRoute;
} else {
@@ -374,10 +380,12 @@ export class AotPlugin implements Tapable {
});
})
.then(() => {
this._compilerHost.resetChangedFileTracker();

// Only turn this off for the first successful run.
this._firstRun = false;
cb();
}, (err: any) => {
this._firstRun = false;
compilation.errors.push(err);
cb();
});
18 changes: 6 additions & 12 deletions packages/angular-cli/models/webpack-build-common.ts
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ export function getWebpackCommonConfig(
) {

const appRoot = path.resolve(projectRoot, appConfig.root);
const nodeModules = path.join(projectRoot, 'node_modules');
const nodeModules = path.resolve(projectRoot, 'node_modules');

let extraPlugins: any[] = [];
let extraRules: any[] = [];
@@ -56,8 +56,6 @@ export function getWebpackCommonConfig(
entryPoints['polyfills'] = [path.resolve(appRoot, appConfig.polyfills)];
}

entryPoints['angular'] = [path.resolve(appRoot, '../node_modules/@angular/core/index.js')];

// determine hashing format
const hashFormat = getOutputHashFormat(outputHashing);

@@ -74,15 +72,11 @@ export function getWebpackCommonConfig(
}

if (vendorChunk) {
extraPlugins.push(
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
chunks: ['main'],
minChunks: (module: any) => {
return module.resource && module.resource.startsWith(nodeModules);
}
}),
);
extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
chunks: ['main'],
minChunks: (module: any) => module.resource && module.resource.startsWith(nodeModules)
}));
}

// process environment file replacement
67 changes: 61 additions & 6 deletions tests/e2e/tests/build/rebuild.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,72 @@
import {killAllProcesses, exec, waitForAnyProcessOutputToMatch} from '../../utils/process';
import {ngServe} from '../../utils/project';
import {request} from '../../utils/http';
import {
killAllProcesses,
exec,
waitForAnyProcessOutputToMatch,
silentExecAndWaitForOutputToMatch,
ng, execAndWaitForOutputToMatch,
} from '../../utils/process';
import {writeFile} from '../../utils/fs';
import {wait} from '../../utils/utils';


export default function() {
if (process.platform.startsWith('win')) {
return Promise.resolve();
}

return ngServe()
let oldNumberOfChunks = 0;
const chunkRegExp = /chunk\s+\{/g;

return execAndWaitForOutputToMatch('ng', ['serve'], /webpack: bundle is now VALID/)
// Should trigger a rebuild.
.then(() => exec('touch', 'src/main.ts'))
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now VALID/, 10000))
.then(() => request('http://localhost:4200/'))
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now INVALID/, 1000))
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now VALID/, 5000))
// Count the bundles.
.then((stdout: string) => {
oldNumberOfChunks = stdout.split(chunkRegExp).length;
})
// Add a lazy module.
.then(() => ng('generate', 'module', 'lazy', '--routing'))
// Just wait for the rebuild, otherwise we might be validating this build.
.then(() => wait(1000))
.then(() => writeFile('src/app/app.module.ts', `
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
RouterModule.forRoot([
{ path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule' }
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
`))
// Should trigger a rebuild with a new bundle.
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now INVALID/, 1000))
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now VALID/, 5000))
// Count the bundles.
.then((stdout: string) => {
let newNumberOfChunks = stdout.split(chunkRegExp).length;
console.log(oldNumberOfChunks, newNumberOfChunks);
if (oldNumberOfChunks >= newNumberOfChunks) {
throw new Error('Expected webpack to create a new chunk, but did not.');
}
})
.then(() => killAllProcesses(), (err: any) => {
killAllProcesses();
throw err;
1 change: 1 addition & 0 deletions tests/e2e/tests/packages/webpack/test.ts
Original file line number Diff line number Diff line change
@@ -16,5 +16,6 @@ export default function(skipCleaning: () => void) {
.then(() => replaceInFile('app/app.component.ts',
'./app.component.scss', 'app.component.scss'))
.then(() => exec(normalize('node_modules/.bin/webpack'), '-p'))
// test
.then(() => skipCleaning());
}
8 changes: 5 additions & 3 deletions tests/e2e/utils/process.ts
Original file line number Diff line number Diff line change
@@ -83,9 +83,9 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise<strin
});
}

export function waitForAnyProcessOutputToMatch(match: RegExp, timeout = 30000) {
export function waitForAnyProcessOutputToMatch(match: RegExp, timeout = 30000): Promise<string> {
// Race between _all_ processes, and the timeout. First one to resolve/reject wins.
return Promise.race(_processes.map(childProcess => new Promise((resolve, reject) => {
return Promise.race(_processes.map(childProcess => new Promise(resolve => {
let stdout = '';
childProcess.stdout.on('data', (data: Buffer) => {
stdout += data.toString();
@@ -96,7 +96,9 @@ export function waitForAnyProcessOutputToMatch(match: RegExp, timeout = 30000) {
})).concat([
new Promise((resolve, reject) => {
// Wait for 30 seconds and timeout.
setTimeout(reject, timeout);
setTimeout(() => {
reject(new Error(`Waiting for ${match} timed out (timeout: ${timeout}msec)...`));
}, timeout);
})
]));
}

0 comments on commit 4f43b82

Please sign in to comment.