fix(@angular-devkit/build-angular): run build steps for differential loading in sequence to avoid confusing progress information

Before, the build tasks ran in parallel and so the different webpack
instances competed over the same lines on the console.

To fail fast and to prevent to show the same errors twice, the second
build step is not executed if the first one fails.

As running these tasks in sequence causes issues with watch mode, this
PR also disables differential loading when watch mode is requested.
This commit is contained in:
ManfredSteyer 2019-04-18 20:28:12 +02:00 committed by Minko Gechev
parent 4807ff00d1
commit 201856a5ec
3 changed files with 47 additions and 10 deletions

View File

@ -10,7 +10,7 @@ import {
BuilderOutput, BuilderOutput,
createBuilder, createBuilder,
} from '@angular-devkit/architect'; } from '@angular-devkit/architect';
import { WebpackLoggingCallback, runWebpack } from '@angular-devkit/build-webpack'; import { BuildResult, WebpackLoggingCallback, runWebpack } from '@angular-devkit/build-webpack';
import { import {
experimental, experimental,
join, join,
@ -23,8 +23,8 @@ import {
import { NodeJsSyncHost } from '@angular-devkit/core/node'; import { NodeJsSyncHost } from '@angular-devkit/core/node';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { from, of, zip } from 'rxjs'; import { concat, from, of, zip } from 'rxjs';
import { catchError, concatMap, map, switchMap } from 'rxjs/operators'; import { bufferCount, catchError, concatMap, map, mergeScan, switchMap } from 'rxjs/operators';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import { NgBuildAnalyticsPlugin } from '../../plugins/webpack/analytics'; import { NgBuildAnalyticsPlugin } from '../../plugins/webpack/analytics';
import { WebpackConfigOptions } from '../angular-cli-files/models/build-options'; import { WebpackConfigOptions } from '../angular-cli-files/models/build-options';
@ -182,12 +182,18 @@ export function buildWebpackBrowser(
normalize(workspace.getProject(projectName).root), normalize(workspace.getProject(projectName).root),
); );
// We use zip because when having multiple builds we want to wait return from(configs).pipe(
// for all builds to finish before processeding // the concurrency parameter (3rd parameter of mergeScan) is deliberately
return zip( // set to 1 to make sure the build steps are executed in sequence.
...configs.map(config => runWebpack(config, context, { logging: loggingFn })), mergeScan((lastResult, config) => {
) // Make sure to only run the 2nd build step, if 1st one succeeded
.pipe( if (lastResult.success) {
return runWebpack(config, context, { logging: loggingFn });
} else {
return of();
}
}, { success: true } as BuildResult, 1),
bufferCount(configs.length),
switchMap(buildEvents => { switchMap(buildEvents => {
const success = buildEvents.every(r => r.success); const success = buildEvents.every(r => r.success);
if (success && buildEvents.length === 2 && options.index) { if (success && buildEvents.length === 2 && options.index) {

View File

@ -55,7 +55,8 @@ export async function generateWebpackConfig(
// However this config generation is used by multiple builders such as dev-server // However this config generation is used by multiple builders such as dev-server
const scriptTarget = tsConfig.options.target; const scriptTarget = tsConfig.options.target;
const differentialLoading = context.builder.builderName === 'browser' const differentialLoading = context.builder.builderName === 'browser'
&& isDifferentialLoadingNeeded(projectRoot, scriptTarget); && isDifferentialLoadingNeeded(projectRoot, scriptTarget) && !options.watch;
const scriptTargets = [scriptTarget]; const scriptTargets = [scriptTarget];
if (differentialLoading) { if (differentialLoading) {

View File

@ -63,6 +63,36 @@ describe('Browser Builder with differential loading', () => {
.toEqual(jasmine.arrayWithExactContents(expectedOutputs)); .toEqual(jasmine.arrayWithExactContents(expectedOutputs));
}); });
it('deactivates differential loading for watch mode', async () => {
const { files } = await browserBuild(architect, host, target, { watch: true });
const expectedOutputs = [
'favicon.ico',
'index.html',
'main.js',
'main.js.map',
'polyfills-es5.js',
'polyfills-es5.js.map',
'polyfills.js',
'polyfills.js.map',
'runtime.js',
'runtime.js.map',
'styles.js',
'styles.js.map',
'vendor.js',
'vendor.js.map',
] as PathFragment[];
expect(Object.keys(files))
.toEqual(jasmine.arrayWithExactContents(expectedOutputs));
});
it('emits the right ES formats', async () => { it('emits the right ES formats', async () => {
const { files } = await browserBuild(architect, host, target, { optimization: true }); const { files } = await browserBuild(architect, host, target, { optimization: true });
expect(await files['main-es5.js']).not.toContain('class'); expect(await files['main-es5.js']).not.toContain('class');