Skip to content

Commit 4f43b82

Browse files
committed
.
1 parent e69dd03 commit 4f43b82

File tree

6 files changed

+98
-35
lines changed

6 files changed

+98
-35
lines changed

packages/@ngtools/webpack/src/lazy_routes.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {dirname, join} from 'path';
12
import * as ts from 'typescript';
23

34
import {TypeScriptFileRefactor} from './refactor';
@@ -34,7 +35,7 @@ export function findLazyRoutes(filePath: string,
3435
// Take all `loadChildren` elements.
3536
.reduce((acc: ts.PropertyAssignment[], props: ts.PropertyAssignment[]) => {
3637
return acc.concat(props.filter(literal => {
37-
return _getContentOfKeyLiteral(refactor.sourceFile, literal) == 'loadChildren';
38+
return _getContentOfKeyLiteral(refactor.sourceFile, literal.name) == 'loadChildren';
3839
}));
3940
}, [])
4041
// Get only string values.
@@ -45,12 +46,14 @@ export function findLazyRoutes(filePath: string,
4546
// does not exist.
4647
.map((routePath: string) => {
4748
const moduleName = routePath.split('#')[0];
48-
const resolvedModuleName = ts.resolveModuleName(
49-
moduleName, filePath, program.getCompilerOptions(), host);
49+
const resolvedModuleName: ts.ResolvedModuleWithFailedLookupLocations = moduleName[0] == '.'
50+
? { resolvedModule: { resolvedFileName: join(dirname(filePath), moduleName) + '.ts' },
51+
failedLookupLocations: [] }
52+
: ts.resolveModuleName(moduleName, filePath, program.getCompilerOptions(), host);
5053
if (resolvedModuleName.resolvedModule
5154
&& resolvedModuleName.resolvedModule.resolvedFileName
5255
&& host.fileExists(resolvedModuleName.resolvedModule.resolvedFileName)) {
53-
return [routePath, resolvedModuleName];
56+
return [routePath, resolvedModuleName.resolvedModule.resolvedFileName];
5457
} else {
5558
return [routePath, null];
5659
}

packages/@ngtools/webpack/src/plugin.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class AotPlugin implements Tapable {
4040
private _rootFilePath: string[];
4141
private _compilerHost: WebpackCompilerHost;
4242
private _resourceLoader: WebpackResourceLoader;
43-
private _lazyRoutes: { [route: string]: string } = Object.create(null);
43+
private _lazyRoutes: LazyRouteMap = Object.create(null);
4444
private _tsConfigPath: string;
4545
private _entryModule: string;
4646

@@ -201,19 +201,22 @@ export class AotPlugin implements Tapable {
201201
private _findLazyRoutesInAst(): LazyRouteMap {
202202
const result: LazyRouteMap = Object.create(null);
203203
const changedFilePaths = this._compilerHost.getChangedFilePaths();
204-
for (const filePath in changedFilePaths) {
204+
for (const filePath of changedFilePaths) {
205205
const fileLazyRoutes = findLazyRoutes(filePath, this._program, this._compilerHost);
206206
for (const routeKey of Object.keys(fileLazyRoutes)) {
207207
const route = fileLazyRoutes[routeKey];
208208
if (routeKey in this._lazyRoutes) {
209209
if (route === null) {
210210
this._lazyRoutes[routeKey] = null;
211211
} else if (this._lazyRoutes[routeKey] !== route) {
212-
throw new Error(`Duplicated path in loadChildren detected: "${routeKey}" is used in 2 `
213-
+ `loadChildren, but they point to different modules `
214-
+ `("${this._lazyRoutes[routeKey]}" and "${route}"). Webpack cannot `
215-
+ `distinguish on context and would fail to load the proper one.`);
212+
this._compilation.warnings.push(
213+
new Error(`Duplicated path in loadChildren detected during a rebuild. `
214+
+ `We will take the latest version detected and override it to save rebuild time. `
215+
+ `You should perform a full build to validate that your routes don't overlap.`)
216+
);
216217
}
218+
} else {
219+
result[routeKey] = route;
217220
}
218221
}
219222
}
@@ -346,13 +349,12 @@ export class AotPlugin implements Tapable {
346349
.then(() => {
347350
// Populate the file system cache with the virtual module.
348351
this._compilerHost.populateWebpackResolver(this._compiler.resolvers.normal);
349-
this._compilerHost.resetChangedFileTracker();
350352
})
351353
.then(() => {
352354
// We need to run the `listLazyRoutes` the first time because it also navigates libraries
353355
// and other things that we might miss using the findLazyRoutesInAst.
354-
let discoveredLazyRoutes: LazyRouteMap = this.firstRun
355-
? __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
356+
let discoveredLazyRoutes: LazyRouteMap = this.firstRun ?
357+
__NGTOOLS_PRIVATE_API_2.listLazyRoutes({
356358
program: this._program,
357359
host: this._compilerHost,
358360
angularCompilerOptions: this._angularCompilerOptions,
@@ -365,6 +367,10 @@ export class AotPlugin implements Tapable {
365367
.forEach(k => {
366368
const lazyRoute = discoveredLazyRoutes[k];
367369
k = k.split('#')[0];
370+
if (lazyRoute === null) {
371+
return;
372+
}
373+
368374
if (this.skipCodeGeneration) {
369375
this._lazyRoutes[k] = lazyRoute;
370376
} else {
@@ -374,10 +380,12 @@ export class AotPlugin implements Tapable {
374380
});
375381
})
376382
.then(() => {
383+
this._compilerHost.resetChangedFileTracker();
384+
385+
// Only turn this off for the first successful run.
377386
this._firstRun = false;
378387
cb();
379388
}, (err: any) => {
380-
this._firstRun = false;
381389
compilation.errors.push(err);
382390
cb();
383391
});

packages/angular-cli/models/webpack-build-common.ts

+6-12
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function getWebpackCommonConfig(
3636
) {
3737

3838
const appRoot = path.resolve(projectRoot, appConfig.root);
39-
const nodeModules = path.join(projectRoot, 'node_modules');
39+
const nodeModules = path.resolve(projectRoot, 'node_modules');
4040

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

59-
entryPoints['angular'] = [path.resolve(appRoot, '../node_modules/@angular/core/index.js')];
60-
6159
// determine hashing format
6260
const hashFormat = getOutputHashFormat(outputHashing);
6361

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

7674
if (vendorChunk) {
77-
extraPlugins.push(
78-
new webpack.optimize.CommonsChunkPlugin({
79-
name: 'vendor',
80-
chunks: ['main'],
81-
minChunks: (module: any) => {
82-
return module.resource && module.resource.startsWith(nodeModules);
83-
}
84-
}),
85-
);
75+
extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({
76+
name: 'vendor',
77+
chunks: ['main'],
78+
minChunks: (module: any) => module.resource && module.resource.startsWith(nodeModules)
79+
}));
8680
}
8781

8882
// process environment file replacement

tests/e2e/tests/build/rebuild.ts

+61-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,72 @@
1-
import {killAllProcesses, exec, waitForAnyProcessOutputToMatch} from '../../utils/process';
2-
import {ngServe} from '../../utils/project';
3-
import {request} from '../../utils/http';
1+
import {
2+
killAllProcesses,
3+
exec,
4+
waitForAnyProcessOutputToMatch,
5+
silentExecAndWaitForOutputToMatch,
6+
ng, execAndWaitForOutputToMatch,
7+
} from '../../utils/process';
8+
import {writeFile} from '../../utils/fs';
9+
import {wait} from '../../utils/utils';
410

511

612
export default function() {
713
if (process.platform.startsWith('win')) {
814
return Promise.resolve();
915
}
1016

11-
return ngServe()
17+
let oldNumberOfChunks = 0;
18+
const chunkRegExp = /chunk\s+\{/g;
19+
20+
return execAndWaitForOutputToMatch('ng', ['serve'], /webpack: bundle is now VALID/)
21+
// Should trigger a rebuild.
1222
.then(() => exec('touch', 'src/main.ts'))
13-
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now VALID/, 10000))
14-
.then(() => request('http://localhost:4200/'))
23+
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now INVALID/, 1000))
24+
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now VALID/, 5000))
25+
// Count the bundles.
26+
.then((stdout: string) => {
27+
oldNumberOfChunks = stdout.split(chunkRegExp).length;
28+
})
29+
// Add a lazy module.
30+
.then(() => ng('generate', 'module', 'lazy', '--routing'))
31+
// Just wait for the rebuild, otherwise we might be validating this build.
32+
.then(() => wait(1000))
33+
.then(() => writeFile('src/app/app.module.ts', `
34+
import { BrowserModule } from '@angular/platform-browser';
35+
import { NgModule } from '@angular/core';
36+
import { FormsModule } from '@angular/forms';
37+
import { HttpModule } from '@angular/http';
38+
39+
import { AppComponent } from './app.component';
40+
import { RouterModule } from '@angular/router';
41+
42+
@NgModule({
43+
declarations: [
44+
AppComponent
45+
],
46+
imports: [
47+
BrowserModule,
48+
FormsModule,
49+
HttpModule,
50+
RouterModule.forRoot([
51+
{ path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule' }
52+
])
53+
],
54+
providers: [],
55+
bootstrap: [AppComponent]
56+
})
57+
export class AppModule { }
58+
`))
59+
// Should trigger a rebuild with a new bundle.
60+
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now INVALID/, 1000))
61+
.then(() => waitForAnyProcessOutputToMatch(/webpack: bundle is now VALID/, 5000))
62+
// Count the bundles.
63+
.then((stdout: string) => {
64+
let newNumberOfChunks = stdout.split(chunkRegExp).length;
65+
console.log(oldNumberOfChunks, newNumberOfChunks);
66+
if (oldNumberOfChunks >= newNumberOfChunks) {
67+
throw new Error('Expected webpack to create a new chunk, but did not.');
68+
}
69+
})
1570
.then(() => killAllProcesses(), (err: any) => {
1671
killAllProcesses();
1772
throw err;

tests/e2e/tests/packages/webpack/test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ export default function(skipCleaning: () => void) {
1616
.then(() => replaceInFile('app/app.component.ts',
1717
'./app.component.scss', 'app.component.scss'))
1818
.then(() => exec(normalize('node_modules/.bin/webpack'), '-p'))
19+
// test
1920
.then(() => skipCleaning());
2021
}

tests/e2e/utils/process.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise<strin
8383
});
8484
}
8585

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

0 commit comments

Comments
 (0)