import { joinPathFragments, parseTargetString, readCachedProjectGraph, } from '@nrwl/devkit'; import { WebpackNxBuildCoordinationPlugin } from '@nrwl/webpack/src/plugins/webpack-nx-build-coordination-plugin'; import { DependentBuildableProjectNode } from '@nrwl/js/src/utils/buildable-libs-utils'; import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph'; import { existsSync } from 'fs'; import { isNpmProject } from 'nx/src/project-graph/operators'; import { mergeCustomWebpackConfig, resolveIndexHtmlTransformer, } from '../utilities/webpack'; import { normalizeOptions } from './lib'; import type { Schema } from './schema'; import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs'; import { from } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { getRootTsConfigPath } from 'nx/src/utils/typescript'; export function executeWebpackDevServerBuilder( rawOptions: Schema, context: import('@angular-devkit/architect').BuilderContext ) { process.env.NX_TSCONFIG_PATH = getRootTsConfigPath(); const options = normalizeOptions(rawOptions); const parsedBrowserTarget = parseTargetString( options.browserTarget, readCachedProjectGraph() ); const browserTargetProjectConfiguration = readCachedProjectConfiguration( parsedBrowserTarget.project ); const buildTarget = browserTargetProjectConfiguration.targets[parsedBrowserTarget.target]; const buildTargetConfiguration = parsedBrowserTarget.configuration ? buildTarget.configurations[parsedBrowserTarget.configuration] : buildTarget.defaultConfiguration ? buildTarget.configurations[buildTarget.defaultConfiguration] : buildTarget.options; const buildLibsFromSource = options.buildLibsFromSource ?? buildTargetConfiguration?.buildLibsFromSource ?? buildTarget.options.buildLibsFromSource ?? true; const customWebpackConfig: { path: string } = buildTargetConfiguration?.customWebpackConfig ?? buildTarget.options.customWebpackConfig; let pathToWebpackConfig: string; if (customWebpackConfig && customWebpackConfig.path) { pathToWebpackConfig = 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 indexFileTransformer: string = buildTargetConfiguration?.indexFileTransformer ?? buildTarget.options.indexFileTransformer; let pathToIndexFileTransformer: string; if (indexFileTransformer) { pathToIndexFileTransformer = joinPathFragments( context.workspaceRoot, indexFileTransformer ); if (pathToIndexFileTransformer && !existsSync(pathToIndexFileTransformer)) { throw new Error( `File containing Index File Transformer function Not Found!\n Please ensure the path to the file containing the function is correct: \n${pathToIndexFileTransformer}` ); } } let dependencies: DependentBuildableProjectNode[]; if (!buildLibsFromSource) { const buildTargetTsConfigPath = buildTargetConfiguration?.tsConfig ?? buildTarget.options.tsConfig; const { tsConfigPath, dependencies: foundDependencies } = createTmpTsConfigForBuildableLibs(buildTargetTsConfigPath, context, { target: parsedBrowserTarget.target, }); dependencies = foundDependencies; // 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 = tsConfigPath; return options; }; // The buildTargetConfiguration also needs to use the generated tsconfig path // otherwise the build will fail if customWebpack function/file is referencing // local libs. This synchronize the behavior with webpack-browser and // webpack-server implementation. buildTargetConfiguration.tsConfig = tsConfigPath; } return from(import('@angular-devkit/build-angular')).pipe( switchMap(({ executeDevServerBuilder }) => executeDevServerBuilder(options, context, { webpackConfiguration: async (baseWebpackConfig) => { if (!buildLibsFromSource) { 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) { baseWebpackConfig.plugins.push( new WebpackNxBuildCoordinationPlugin( `nx run-many --target=${ parsedBrowserTarget.target } --projects=${workspaceDependencies.join(',')}` ) ); } } if (!pathToWebpackConfig) { return baseWebpackConfig; } return mergeCustomWebpackConfig( baseWebpackConfig, pathToWebpackConfig, buildTargetConfiguration, context.target ); }, ...(pathToIndexFileTransformer ? { indexHtml: resolveIndexHtmlTransformer( pathToIndexFileTransformer, buildTargetConfiguration.tsConfig, context.target ), } : {}), }) ) ); } export default require('@angular-devkit/architect').createBuilder( executeWebpackDevServerBuilder ) as any;