diff --git a/docs/generated/packages/angular.json b/docs/generated/packages/angular.json index 2bc0cb20b9b90..a94ee3aac791e 100644 --- a/docs/generated/packages/angular.json +++ b/docs/generated/packages/angular.json @@ -2999,6 +2999,11 @@ "poll": { "type": "number", "description": "Enable and define the file watching poll time period in milliseconds." + }, + "buildLibsFromSource": { + "type": "boolean", + "description": "Read buildable libraries from source instead of building them separately.", + "default": true } }, "additionalProperties": false, diff --git a/packages/angular/package.json b/packages/angular/package.json index 49fe79a0b672a..148020273a598 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -43,6 +43,7 @@ "@nrwl/jest": "file:../jest", "@nrwl/linter": "file:../linter", "@nrwl/storybook": "file:../storybook", + "@nrwl/web": "file:../web", "@nrwl/workspace": "file:../workspace", "@phenomnomnominal/tsquery": "4.1.1", "@schematics/angular": "~14.0.0", diff --git a/packages/angular/src/builders/webpack-server/lib/normalize-options.ts b/packages/angular/src/builders/webpack-server/lib/normalize-options.ts index b8cc2b3fd2f28..9401ccd3d3992 100644 --- a/packages/angular/src/builders/webpack-server/lib/normalize-options.ts +++ b/packages/angular/src/builders/webpack-server/lib/normalize-options.ts @@ -7,6 +7,7 @@ export function normalizeOptions(schema: Schema): Schema { liveReload: true, open: false, ssl: false, + buildLibsFromSource: true, ...schema, }; } diff --git a/packages/angular/src/builders/webpack-server/schema.d.ts b/packages/angular/src/builders/webpack-server/schema.d.ts index 5ebdefd1fb25b..f534977441bf7 100644 --- a/packages/angular/src/builders/webpack-server/schema.d.ts +++ b/packages/angular/src/builders/webpack-server/schema.d.ts @@ -17,4 +17,5 @@ export interface Schema { hmr?: boolean; watch?: boolean; poll?: number; + buildLibsFromSource?: boolean; } diff --git a/packages/angular/src/builders/webpack-server/schema.json b/packages/angular/src/builders/webpack-server/schema.json index 2dd9ae9622a3d..00834f527f0b8 100644 --- a/packages/angular/src/builders/webpack-server/schema.json +++ b/packages/angular/src/builders/webpack-server/schema.json @@ -102,6 +102,11 @@ "poll": { "type": "number", "description": "Enable and define the file watching poll time period in milliseconds." + }, + "buildLibsFromSource": { + "type": "boolean", + "description": "Read buildable libraries from source instead of building them separately.", + "default": true } }, "additionalProperties": false, diff --git a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts index 737625cbc0ea6..8cb626946baa3 100644 --- a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts +++ b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts @@ -1,33 +1,40 @@ -import { BuilderContext, createBuilder } from '@angular-devkit/architect'; +import { + BuilderContext, + BuilderOutput, + createBuilder, +} from '@angular-devkit/architect'; import { DevServerBuilderOptions, - serveWebpackBrowser, -} from '@angular-devkit/build-angular/src/builders/dev-server'; + executeDevServerBuilder, +} from '@angular-devkit/build-angular'; import { JsonObject } from '@angular-devkit/core'; import { joinPathFragments, parseTargetString, readAllWorkspaceConfiguration, - Workspaces, + readCachedProjectGraph, } from '@nrwl/devkit'; +import { WebpackNxBuildCoordinationPlugin } from '@nrwl/web/src/plugins/webpack-nx-build-coordination-plugin'; +import { + calculateProjectDependencies, + createTmpTsConfig, + DependentBuildableProjectNode, +} from '@nrwl/workspace/src/utilities/buildable-libs-utils'; import { existsSync } from 'fs'; +import { isNpmProject } from 'nx/src/project-graph/operators'; +import { Observable } from 'rxjs'; import { merge } from 'webpack-merge'; import { resolveCustomWebpackConfig } from '../utilities/webpack'; import { normalizeOptions } from './lib'; import type { Schema } from './schema'; export function executeWebpackServerBuilder( - schema: Schema, + rawOptions: Schema, context: BuilderContext -) { - process.env.NX_TSCONFIG_PATH = joinPathFragments( - context.workspaceRoot, - 'tsconfig.base.json' - ); - - const options = normalizeOptions(schema); +): Observable { const workspaceConfig = readAllWorkspaceConfiguration(); + const options = normalizeOptions(rawOptions); const parsedBrowserTarget = parseTargetString(options.browserTarget); const buildTarget = workspaceConfig.projects[parsedBrowserTarget.project].targets[ @@ -44,52 +51,91 @@ export function executeWebpackServerBuilder( selectedConfiguration.customWebpackConfig ?? buildTarget.options.customWebpackConfig; + let pathToWebpackConfig: string; if (customWebpackConfig && customWebpackConfig.path) { - const pathToWebpackConfig = joinPathFragments( + pathToWebpackConfig = joinPathFragments( context.workspaceRoot, customWebpackConfig.path ); - if (existsSync(pathToWebpackConfig)) { - return serveWebpackBrowser( - options as DevServerBuilderOptions, - context as any, - { - webpackConfiguration: async (baseWebpackConfig) => { - const customWebpackConfiguration = resolveCustomWebpackConfig( - pathToWebpackConfig, - buildTarget.options.tsConfig - ); - // The extra Webpack configuration file can also export a Promise, for instance: - // `module.exports = new Promise(...)`. If it exports a single object, but not a Promise, - // then await will just resolve that object. - const config = await customWebpackConfiguration; - - // The extra Webpack configuration file can export a synchronous or asynchronous function, - // for instance: `module.exports = async config => { ... }`. - if (typeof config === 'function') { - return config( - baseWebpackConfig, - selectedConfiguration, - context.target - ); - } else { - return merge(baseWebpackConfig, config); - } - }, - } - ); - } else { + if (!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}` ); } } - return serveWebpackBrowser( - options as DevServerBuilderOptions, - context as any - ); + let dependencies: DependentBuildableProjectNode[]; + if (!options.buildLibsFromSource) { + const buildTargetTsConfigPath = + selectedConfiguration.tsConfig ?? buildTarget.options.tsConfig; + const result = calculateProjectDependencies( + readCachedProjectGraph(), + context.workspaceRoot, + context.target.project, + parsedBrowserTarget.target, + context.target.configuration + ); + dependencies = result.dependencies; + const updatedTsConfig = createTmpTsConfig( + joinPathFragments(context.workspaceRoot, buildTargetTsConfigPath), + context.workspaceRoot, + result.target.data.root, + dependencies + ); + process.env.NX_TSCONFIG_PATH = updatedTsConfig; + + // We can't just pass the tsconfig path in memory to the angular builder + // function because we can't pass the build target options to it, the build + // targets options will be retrieved by the builder from the project + // configuration. Therefore, we patch the method in the context to retrieve + // the target options to overwrite the tsconfig path to use the generated + // one with the updated path mappings. + const originalGetTargetOptions = context.getTargetOptions; + context.getTargetOptions = async (target) => { + const options = await originalGetTargetOptions(target); + options.tsConfig = updatedTsConfig; + return options; + }; + } + + return executeDevServerBuilder(options as DevServerBuilderOptions, context, { + webpackConfiguration: async (baseWebpackConfig) => { + if (!options.buildLibsFromSource) { + const workspaceDependencies = dependencies + .filter((dep) => !isNpmProject(dep.node)) + .map((dep) => dep.node.name); + baseWebpackConfig.plugins.push( + new WebpackNxBuildCoordinationPlugin( + `nx run-many --target=${ + parsedBrowserTarget.target + } --projects=${workspaceDependencies.join(',')}` + ) + ); + } + + if (!pathToWebpackConfig) { + return baseWebpackConfig; + } + + const customWebpackConfiguration = resolveCustomWebpackConfig( + pathToWebpackConfig, + buildTarget.options.tsConfig + ); + // The extra Webpack configuration file can also export a Promise, for instance: + // `module.exports = new Promise(...)`. If it exports a single object, but not a Promise, + // then await will just resolve that object. + const config = await customWebpackConfiguration; + + // The extra Webpack configuration file can export a synchronous or asynchronous function, + // for instance: `module.exports = async config => { ... }`. + if (typeof config === 'function') { + return config(baseWebpackConfig, selectedConfiguration, context.target); + } + + return merge(baseWebpackConfig, config); + }, + }); } export default createBuilder(