diff --git a/packages/node/src/executors/webpack/webpack.impl.ts b/packages/node/src/executors/webpack/webpack.impl.ts index 8457bdbaae3c0..bdc71d4392d5c 100644 --- a/packages/node/src/executors/webpack/webpack.impl.ts +++ b/packages/node/src/executors/webpack/webpack.impl.ts @@ -1,23 +1,23 @@ -import { ExecutorContext, readCachedProjectGraph } from '@nrwl/devkit'; -import { getHelperDependenciesFromProjectGraph } from '@nrwl/js/src/utils/compiler-helper-dependency'; +import 'dotenv/config'; +import { ExecutorContext } from '@nrwl/devkit'; + +import { readCachedProjectGraph } from '@nrwl/devkit'; import { calculateProjectDependencies, checkDependentProjectsHaveBeenBuilt, createTmpTsConfig, } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; import { getRootTsConfigPath } from '@nrwl/workspace/src/utilities/typescript'; -import 'dotenv/config'; -import { resolve } from 'path'; -import { eachValueFrom } from 'rxjs-for-await'; import { map, tap } from 'rxjs/operators'; +import { eachValueFrom } from 'rxjs-for-await'; +import { resolve } from 'path'; import { register } from 'ts-node'; -import { generatePackageJson } from '../../utils/generate-package-json'; import { getNodeWebpackConfig } from '../../utils/node.config'; +import { BuildNodeBuilderOptions } from '../../utils/types'; import { normalizeBuildOptions } from '../../utils/normalize'; import { runWebpack } from '../../utils/run-webpack'; -import { BuildNodeBuilderOptions } from '../../utils/types'; export type NodeBuildEvent = { outfile: string; @@ -77,17 +77,6 @@ export async function* webpackExecutor( } } - if (options.generatePackageJson) { - const helperDependencies = getHelperDependenciesFromProjectGraph( - context.root, - context.projectName - ); - if (helperDependencies.length > 0) { - projGraph.dependencies[context.projectName] = - projGraph.dependencies[context.projectName].concat(helperDependencies); - } - generatePackageJson(context.projectName, projGraph, options); - } const config = await options.webpackConfig.reduce( async (currentConfig, plugin) => { return require(plugin)(await currentConfig, { @@ -95,7 +84,7 @@ export async function* webpackExecutor( configuration: context.configurationName, }); }, - Promise.resolve(getNodeWebpackConfig(options)) + Promise.resolve(getNodeWebpackConfig(context, projGraph, options)) ); return yield* eachValueFrom( diff --git a/packages/node/src/utils/generate-package-json-webpack-plugin.ts b/packages/node/src/utils/generate-package-json-webpack-plugin.ts new file mode 100644 index 0000000000000..22060035b3f48 --- /dev/null +++ b/packages/node/src/utils/generate-package-json-webpack-plugin.ts @@ -0,0 +1,59 @@ +import { type Compiler, sources, type WebpackPluginInstance } from 'webpack'; +import { + ExecutorContext, + type ProjectGraph, + serializeJson, +} from '@nrwl/devkit'; +import { createPackageJson } from '@nrwl/workspace/src/utilities/create-package-json'; + +import type { BuildNodeBuilderOptions } from './types'; +import { getHelperDependenciesFromProjectGraph } from '@nrwl/js/src/utils/compiler-helper-dependency'; + +export class GeneratePackageJsonWebpackPlugin implements WebpackPluginInstance { + constructor( + private readonly context: ExecutorContext, + private readonly projectGraph: ProjectGraph, + private readonly options: BuildNodeBuilderOptions + ) {} + + apply(compiler: Compiler): void { + const pluginName = this.constructor.name; + + compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { + compilation.hooks.processAssets.tap( + { + name: 'nx-generate-package-json-plugin', + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, + }, + () => { + const helperDependencies = getHelperDependenciesFromProjectGraph( + this.context.root, + this.context.projectName + ); + + if (helperDependencies.length > 0) { + this.projectGraph.dependencies[this.context.projectName] = + this.projectGraph.dependencies[this.context.projectName].concat( + helperDependencies + ); + } + + const packageJson = createPackageJson( + this.context.projectName, + this.projectGraph, + this.options + ); + + packageJson.main = packageJson.main ?? this.options.outputFileName; + + delete packageJson.devDependencies; + + compilation.emitAsset( + 'package.json', + new sources.RawSource(serializeJson(packageJson)) + ); + } + ); + }); + } +} diff --git a/packages/node/src/utils/generate-package-json.ts b/packages/node/src/utils/generate-package-json.ts deleted file mode 100644 index add571b9eb980..0000000000000 --- a/packages/node/src/utils/generate-package-json.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ProjectGraph } from '@nrwl/devkit'; -import { writeJsonFile } from '@nrwl/devkit'; - -import { BuildNodeBuilderOptions } from './types'; -import { createPackageJson } from '@nrwl/workspace/src/utilities/create-package-json'; - -export function generatePackageJson( - projectName: string, - graph: ProjectGraph, - options: BuildNodeBuilderOptions -) { - const packageJson = createPackageJson(projectName, graph, options); - packageJson.main = packageJson.main ?? options.outputFileName; - delete packageJson.devDependencies; - writeJsonFile(`${options.outputPath}/package.json`, packageJson); -} diff --git a/packages/node/src/utils/node.config.spec.ts b/packages/node/src/utils/node.config.spec.ts index 470cf0cf2dea3..a071543b40e07 100644 --- a/packages/node/src/utils/node.config.spec.ts +++ b/packages/node/src/utils/node.config.spec.ts @@ -1,6 +1,9 @@ +import { ProjectGraph, ExecutorContext } from '@nrwl/devkit'; import { join } from 'path'; -import { getNodeWebpackConfig } from './node.config'; import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; + +import { GeneratePackageJsonWebpackPlugin } from './generate-package-json-webpack-plugin'; +import { getNodeWebpackConfig } from './node.config'; import { BuildNodeBuilderOptions } from './types'; jest.mock('tsconfig-paths-webpack-plugin'); @@ -11,8 +14,19 @@ jest.mock('@nrwl/devkit', () => ({ })); describe('getNodePartial', () => { + let context: ExecutorContext; + let projectGraph: ProjectGraph; let input: BuildNodeBuilderOptions; beforeEach(() => { + context = { + projectName: 'sample-project', + root: '/root', + cwd: '', + target: {} as any, + isVerbose: false, + workspace: {} as any, + }; + projectGraph = {} as any; input = { main: 'main.ts', outputPath: 'dist', @@ -28,18 +42,18 @@ describe('getNodePartial', () => { describe('unconditionally', () => { it('should target commonjs', () => { - const result = getNodeWebpackConfig(input); + const result = getNodeWebpackConfig(context, projectGraph, input); expect(result.output.libraryTarget).toEqual('commonjs'); }); it('should target node', () => { - const result = getNodeWebpackConfig(input); + const result = getNodeWebpackConfig(context, projectGraph, input); expect(result.target).toEqual('node'); }); it('should not polyfill node apis', () => { - const result = getNodeWebpackConfig(input); + const result = getNodeWebpackConfig(context, projectGraph, input); expect(result.node).toEqual(false); }); @@ -47,7 +61,7 @@ describe('getNodePartial', () => { describe('the optimization option when true', () => { it('should minify', () => { - const result = getNodeWebpackConfig({ + const result = getNodeWebpackConfig(context, projectGraph, { ...input, optimization: true, }); @@ -57,7 +71,7 @@ describe('getNodePartial', () => { }); it('should concatenate modules', () => { - const result = getNodeWebpackConfig({ + const result = getNodeWebpackConfig(context, projectGraph, { ...input, optimization: true, }); @@ -68,14 +82,14 @@ describe('getNodePartial', () => { describe('the externalDependencies option', () => { it('should change all node_modules to commonjs imports', () => { - const result = getNodeWebpackConfig(input); + const result = getNodeWebpackConfig(context, projectGraph, input); const callback = jest.fn(); result.externals[0](null, '@nestjs/core', callback); expect(callback).toHaveBeenCalledWith(null, 'commonjs @nestjs/core'); }); it('should change given module names to commonjs imports but not others', () => { - const result = getNodeWebpackConfig({ + const result = getNodeWebpackConfig(context, projectGraph, { ...input, externalDependencies: ['module1'], }); @@ -87,7 +101,7 @@ describe('getNodePartial', () => { }); it('should not change any modules to commonjs imports', () => { - const result = getNodeWebpackConfig({ + const result = getNodeWebpackConfig(context, projectGraph, { ...input, externalDependencies: 'none', }); @@ -95,4 +109,29 @@ describe('getNodePartial', () => { expect(result.externals).not.toBeDefined(); }); }); + + describe('the generatePackageJson option', () => { + it('should add the GeneratePackageJsonWebpackPlugin plugin', () => { + const result = getNodeWebpackConfig(context, projectGraph, { + ...input, + generatePackageJson: true, + }); + expect( + result.plugins.find( + (plugin) => plugin instanceof GeneratePackageJsonWebpackPlugin + ) + ).toBeTruthy(); + }); + it('should not add the GeneratePackageJsonWebpackPlugin plugin', () => { + const result = getNodeWebpackConfig(context, projectGraph, { + ...input, + generatePackageJson: false, + }); + expect( + result.plugins.find( + (plugin) => plugin instanceof GeneratePackageJsonWebpackPlugin + ) + ).toBeFalsy(); + }); + }); }); diff --git a/packages/node/src/utils/node.config.ts b/packages/node/src/utils/node.config.ts index 1e583b685a0d4..5682aae7592ff 100644 --- a/packages/node/src/utils/node.config.ts +++ b/packages/node/src/utils/node.config.ts @@ -1,13 +1,18 @@ -import { workspaceRoot } from '@nrwl/devkit'; +import { ExecutorContext, ProjectGraph, workspaceRoot } from '@nrwl/devkit'; import { Configuration } from 'webpack'; import { merge } from 'webpack-merge'; import { getBaseWebpackPartial } from './config'; +import { GeneratePackageJsonWebpackPlugin } from './generate-package-json-webpack-plugin'; import { BuildNodeBuilderOptions } from './types'; import nodeExternals = require('webpack-node-externals'); import TerserPlugin = require('terser-webpack-plugin'); -function getNodePartial(options: BuildNodeBuilderOptions) { +function getNodePartial( + context: ExecutorContext, + projectGraph: ProjectGraph, + options: BuildNodeBuilderOptions +) { const webpackConfig: Configuration = { output: { libraryTarget: 'commonjs', @@ -46,9 +51,24 @@ function getNodePartial(options: BuildNodeBuilderOptions) { }, ]; } + + if (options.generatePackageJson) { + webpackConfig.plugins ??= []; + webpackConfig.plugins.push( + new GeneratePackageJsonWebpackPlugin(context, projectGraph, options) + ); + } + return webpackConfig; } -export function getNodeWebpackConfig(options: BuildNodeBuilderOptions) { - return merge([getBaseWebpackPartial(options), getNodePartial(options)]); +export function getNodeWebpackConfig( + context: ExecutorContext, + projectGraph: ProjectGraph, + options: BuildNodeBuilderOptions +) { + return merge([ + getBaseWebpackPartial(options), + getNodePartial(context, projectGraph, options), + ]); }