Files
nrwl--nx/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
T
2026-07-13 12:38:36 +08:00

181 lines
6.3 KiB
TypeScript

import {
joinPathFragments,
normalizePath,
ProjectGraph,
readCachedProjectGraph,
targetToTargetString,
} from '@nx/devkit';
import type { DependentBuildableProjectNode } from '@nx/js/internal';
import { WebpackNxBuildCoordinationPlugin } from '@nx/webpack/internal';
import { existsSync } from 'fs';
import { isNpmProject } from 'nx/src/project-graph/operators';
import { getDependencyConfigs } from 'nx/src/tasks-runner/utils';
import { relative } from 'path';
import { from, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { assertBuilderPackageIsInstalled } from '../../executors/utilities/builder-package';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import {
mergeCustomWebpackConfig,
resolveIndexHtmlTransformer,
} from '../utilities/webpack';
import type { BrowserBuilderSchema } from './schema';
// This is required to ensure that the webpack version used by the Module Federation is the same as the one used by the builders.
const Module = require('module');
const originalResolveFilename = Module._resolveFilename;
const patchedWebpackPath = require.resolve('webpack', {
paths: [require.resolve('@angular-devkit/build-angular')],
});
// Override the resolve function
Module._resolveFilename = function (request, parent, isMain, options) {
// Intercept webpack specifically
if (request === 'webpack') {
// Force webpack to resolve from your specific path
return patchedWebpackPath;
}
// For all other modules, use the original resolver
return originalResolveFilename.call(this, request, parent, isMain, options);
};
function shouldSkipInitialTargetRun(
projectGraph: ProjectGraph,
project: string,
target: string
): boolean {
const allTargetNames = new Set<string>();
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
for (const targetName in project.data.targets ?? {}) {
allTargetNames.add(targetName);
}
}
const projectDependencyConfigs = getDependencyConfigs(
{ project, target },
{},
projectGraph,
Array.from(allTargetNames)
);
// 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> {
options.buildLibsFromSource ??= true;
options.watchDependencies ??= true;
const {
buildLibsFromSource,
customWebpackConfig,
indexHtmlTransformer,
watchDependencies,
...delegateBuilderOptions
} = options;
process.env.NX_BUILD_LIBS_FROM_SOURCE = `${buildLibsFromSource}`;
process.env.NX_BUILD_TARGET = targetToTargetString({ ...context.target });
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 =
indexHtmlTransformer &&
joinPathFragments(context.workspaceRoot, indexHtmlTransformer);
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[];
let projectGraph: ProjectGraph;
if (!buildLibsFromSource) {
projectGraph = readCachedProjectGraph();
const { tsConfigPath, dependencies: foundDependencies } =
createTmpTsConfigForBuildableLibs(
delegateBuilderOptions.tsConfig,
context,
{ projectGraph }
);
dependencies = foundDependencies;
delegateBuilderOptions.tsConfig = normalizePath(
relative(context.workspaceRoot, tsConfigPath)
);
}
assertBuilderPackageIsInstalled('@angular-devkit/build-angular');
return from(import('@angular-devkit/build-angular')).pipe(
switchMap(({ executeBrowserBuilder }) =>
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(
// Cast away the angular/webpack plugin type difference (webpack versions).
new (WebpackNxBuildCoordinationPlugin as any)(
`nx run-many --target=${
context.target.target
} --projects=${workspaceDependencies.join(',')}`,
{ skipInitialRun, skipWatchingDeps: !watchDependencies }
)
);
}
}
if (!pathToWebpackConfig) {
return baseWebpackConfig;
}
return mergeCustomWebpackConfig(
baseWebpackConfig,
pathToWebpackConfig,
delegateBuilderOptions,
context.target
);
},
...(pathToIndexFileTransformer
? {
indexHtml: resolveIndexHtmlTransformer(
pathToIndexFileTransformer,
delegateBuilderOptions.tsConfig,
context.target
),
}
: {}),
})
)
);
}
export default require('@angular-devkit/architect').createBuilder(
executeWebpackBrowserBuilder
) as any;