diff --git a/packages/angular/src/builders/utilities/buildable-libs.ts b/packages/angular/src/builders/utilities/buildable-libs.ts index 3a7ac1e180a5a..02ccdaadb6497 100644 --- a/packages/angular/src/builders/utilities/buildable-libs.ts +++ b/packages/angular/src/builders/utilities/buildable-libs.ts @@ -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; diff --git a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts index c306600218606..db7c54ab28033 100644 --- a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts +++ b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts @@ -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'; @@ -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 { + 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); @@ -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 ), } @@ -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 { - 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; diff --git a/packages/angular/src/builders/webpack-dev-server/webpack-dev-server.impl.ts b/packages/angular/src/builders/webpack-dev-server/webpack-dev-server.impl.ts index 18d52b5391736..af86dc691f9af 100644 --- a/packages/angular/src/builders/webpack-dev-server/webpack-dev-server.impl.ts +++ b/packages/angular/src/builders/webpack-dev-server/webpack-dev-server.impl.ts @@ -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 diff --git a/packages/webpack/src/plugins/webpack-nx-build-coordination-plugin.ts b/packages/webpack/src/plugins/webpack-nx-build-coordination-plugin.ts index 15cc4c3f9cb57..2864c747a5508 100644 --- a/packages/webpack/src/plugins/webpack-nx-build-coordination-plugin.ts +++ b/packages/webpack/src/plugins/webpack-nx-build-coordination-plugin.ts @@ -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(); }