Skip to content

Commit

Permalink
fix(angular): make webpack-browser executor watch changes correctly i…
Browse files Browse the repository at this point in the history
…n incremental builds setups (#15222)
  • Loading branch information
leosvelperez committed Feb 24, 2023
1 parent 281881d commit b488f64
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 87 deletions.
8 changes: 4 additions & 4 deletions packages/angular/src/builders/utilities/buildable-libs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ import {
createTmpTsConfig,
DependentBuildableProjectNode,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { readCachedProjectGraph } from '@nrwl/devkit';
import { ProjectGraph, readCachedProjectGraph } from '@nrwl/devkit';
import { join } from 'path';

export function createTmpTsConfigForBuildableLibs(
tsConfigPath: string,
context: import('@angular-devkit/architect').BuilderContext,
target?: string
options?: { projectGraph?: ProjectGraph; target?: string }
) {
let dependencies: DependentBuildableProjectNode[];
const result = calculateProjectDependencies(
readCachedProjectGraph(),
options?.projectGraph ?? readCachedProjectGraph(),
context.workspaceRoot,
context.target.project,
target ?? context.target.target,
options?.target ?? context.target.target,
context.target.configuration
);
dependencies = result.dependencies;
Expand Down
178 changes: 102 additions & 76 deletions packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { joinPathFragments, stripIndents } from '@nrwl/devkit';
import {
joinPathFragments,
ProjectGraph,
readCachedProjectGraph,
stripIndents,
} from '@nrwl/devkit';
import { WebpackNxBuildCoordinationPlugin } from '@nrwl/webpack/src/plugins/webpack-nx-build-coordination-plugin';
import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { existsSync } from 'fs';
import { readNxJson } from 'nx/src/project-graph/file-utils';
import { isNpmProject } from 'nx/src/project-graph/operators';
import { getDependencyConfigs } from 'nx/src/tasks-runner/utils';
import { from, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
Expand All @@ -18,19 +28,61 @@ export type BrowserBuilderSchema =
buildLibsFromSource?: boolean;
};

function buildApp(
function validateOptions(options: BrowserBuilderSchema): void {
const { major, version } = getInstalledAngularVersionInfo();
if (major < 15 && Array.isArray(options.polyfills)) {
throw new Error(stripIndents`The array syntax for the "polyfills" option is supported from Angular >= 15.0.0. You are currently using ${version}.
You can resolve this error by removing the "polyfills" option, setting it to a string value or migrating to Angular 15.0.0.`);
}
}

function shouldSkipInitialTargetRun(
projectGraph: ProjectGraph,
project: string,
target: string
): boolean {
const nxJson = readNxJson();
const defaultDependencyConfigs = Object.entries(
nxJson.targetDefaults ?? {}
).reduce((acc, [targetName, dependencyConfig]) => {
acc[targetName] = dependencyConfig.dependsOn;
return acc;
}, {});
const projectDependencyConfigs = getDependencyConfigs(
{ project, target },
defaultDependencyConfigs,
projectGraph
);

// if the task runner already ran the target, skip the initial run
return projectDependencyConfigs.some(
(d) => d.target === target && d.projects === 'dependencies'
);
}

export function executeWebpackBrowserBuilder(
options: BrowserBuilderSchema,
context: import('@angular-devkit/architect').BuilderContext
): Observable<import('@angular-devkit/architect').BuilderOutput> {
validateOptions(options);
options.buildLibsFromSource ??= true;

const {
buildLibsFromSource,
customWebpackConfig,
indexFileTransformer,
...delegateOptions
...delegateBuilderOptions
} = options;

// If there is a path to an indexFileTransformer
// check it exists and apply it to the build
const pathToWebpackConfig =
customWebpackConfig?.path &&
joinPathFragments(context.workspaceRoot, customWebpackConfig.path);
if (pathToWebpackConfig && !existsSync(pathToWebpackConfig)) {
throw new Error(
`Custom Webpack Config File Not Found!\nTo use a custom webpack config, please ensure the path to the custom webpack file is correct: \n${pathToWebpackConfig}`
);
}

const pathToIndexFileTransformer =
indexFileTransformer &&
joinPathFragments(context.workspaceRoot, indexFileTransformer);
Expand All @@ -40,66 +92,66 @@ function buildApp(
);
}

// If there is a path to custom webpack config
// Invoke our own support for custom webpack config
if (customWebpackConfig && customWebpackConfig.path) {
const pathToWebpackConfig = joinPathFragments(
context.workspaceRoot,
customWebpackConfig.path
);

if (existsSync(pathToWebpackConfig)) {
return buildAppWithCustomWebpackConfiguration(
delegateOptions,
let dependencies: DependentBuildableProjectNode[];
let projectGraph: ProjectGraph;
if (!buildLibsFromSource) {
projectGraph = readCachedProjectGraph();
const { tsConfigPath, dependencies: foundDependencies } =
createTmpTsConfigForBuildableLibs(
delegateBuilderOptions.tsConfig,
context,
pathToWebpackConfig,
pathToIndexFileTransformer
);
} else {
throw new Error(
`Custom Webpack Config File Not Found!\nTo use a custom webpack config, please ensure the path to the custom webpack file is correct: \n${pathToWebpackConfig}`
{ projectGraph }
);
}
dependencies = foundDependencies;
delegateBuilderOptions.tsConfig = tsConfigPath;
}

return from(import('@angular-devkit/build-angular')).pipe(
switchMap(({ executeBrowserBuilder }) =>
executeBrowserBuilder(delegateOptions, context, {
...(pathToIndexFileTransformer
? {
indexHtml: resolveIndexHtmlTransformer(
pathToIndexFileTransformer,
options.tsConfig,
context.target
),
executeBrowserBuilder(delegateBuilderOptions, context as any, {
webpackConfiguration: (baseWebpackConfig) => {
if (!buildLibsFromSource && delegateBuilderOptions.watch) {
const workspaceDependencies = dependencies
.filter((dep) => !isNpmProject(dep.node))
.map((dep) => dep.node.name);
// default for `nx run-many` is --all projects
// by passing an empty string for --projects, run-many will default to
// run the target for all projects.
// This will occur when workspaceDependencies = []
if (workspaceDependencies.length > 0) {
const skipInitialRun = shouldSkipInitialTargetRun(
projectGraph,
context.target.project,
context.target.target
);

baseWebpackConfig.plugins.push(
new WebpackNxBuildCoordinationPlugin(
`nx run-many --target=${
context.target.target
} --projects=${workspaceDependencies.join(',')}`,
skipInitialRun
)
);
}
: {}),
})
)
);
}
}

function buildAppWithCustomWebpackConfiguration(
options: import('@angular-devkit/build-angular/src/builders/browser/schema').Schema,
context: import('@angular-devkit/architect').BuilderContext,
pathToWebpackConfig: string,
pathToIndexFileTransformer?: string
) {
return from(import('@angular-devkit/build-angular')).pipe(
switchMap(({ executeBrowserBuilder }) =>
executeBrowserBuilder(options, context as any, {
webpackConfiguration: (baseWebpackConfig) =>
mergeCustomWebpackConfig(
if (!pathToWebpackConfig) {
return baseWebpackConfig;
}

return mergeCustomWebpackConfig(
baseWebpackConfig,
pathToWebpackConfig,
options,
delegateBuilderOptions,
context.target
),
);
},
...(pathToIndexFileTransformer
? {
indexHtml: resolveIndexHtmlTransformer(
pathToIndexFileTransformer,
options.tsConfig,
delegateBuilderOptions.tsConfig,
context.target
),
}
Expand All @@ -109,32 +161,6 @@ function buildAppWithCustomWebpackConfiguration(
);
}

function validateOptions(options: BrowserBuilderSchema): void {
const { major, version } = getInstalledAngularVersionInfo();
if (major < 15 && Array.isArray(options.polyfills)) {
throw new Error(stripIndents`The array syntax for the "polyfills" option is supported from Angular >= 15.0.0. You are currently using ${version}.
You can resolve this error by removing the "polyfills" option, setting it to a string value or migrating to Angular 15.0.0.`);
}
}

export function executeWebpackBrowserBuilder(
options: BrowserBuilderSchema,
context: import('@angular-devkit/architect').BuilderContext
): Observable<import('@angular-devkit/architect').BuilderOutput> {
validateOptions(options);
options.buildLibsFromSource ??= true;

if (!options.buildLibsFromSource) {
const { tsConfigPath } = createTmpTsConfigForBuildableLibs(
options.tsConfig,
context
);
options.tsConfig = tsConfigPath;
}

return buildApp(options, context);
}

export default require('@angular-devkit/architect').createBuilder(
executeWebpackBrowserBuilder
) as any;
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ export function executeWebpackDevServerBuilder(
const buildTargetTsConfigPath =
buildTargetConfiguration?.tsConfig ?? buildTarget.options.tsConfig;
const { tsConfigPath, dependencies: foundDependencies } =
createTmpTsConfigForBuildableLibs(
buildTargetTsConfigPath,
context,
parsedBrowserTarget.target
);
createTmpTsConfigForBuildableLibs(buildTargetTsConfigPath, context, {
target: parsedBrowserTarget.target,
});
dependencies = foundDependencies;

// We can't just pass the tsconfig path in memory to the angular builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import type { Compiler } from 'webpack';
export class WebpackNxBuildCoordinationPlugin {
private currentlyRunning: 'none' | 'nx-build' | 'webpack-build' = 'none';

constructor(private readonly buildCmd: string) {
this.buildChangedProjects();
constructor(private readonly buildCmd: string, skipInitialBuild?: boolean) {
if (!skipInitialBuild) {
this.buildChangedProjects();
}
this.startWatchingBuildableLibs();
}

Expand Down

0 comments on commit b488f64

Please sign in to comment.