Skip to content

Commit

Permalink
fix(node): use webpack compilation hooks to emit package.json (#10229)
Browse files Browse the repository at this point in the history
Use the webpack plugin system to create the package.json, this allows for developers to use plugins like
CleanWebpackPlugin which clears the dist before the compilation starts. If this plugin is used in
combination with the old method of generating the package.json, then it is deleted on compilation start.
  • Loading branch information
skrtheboss committed Jun 8, 2022
1 parent 88bcdd0 commit 7418ae1
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 48 deletions.
27 changes: 8 additions & 19 deletions 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;
Expand Down Expand Up @@ -77,25 +77,14 @@ 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, {
options,
configuration: context.configurationName,
});
},
Promise.resolve(getNodeWebpackConfig(options))
Promise.resolve(getNodeWebpackConfig(context, projGraph, options))
);

return yield* eachValueFrom(
Expand Down
59 changes: 59 additions & 0 deletions 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))
);
}
);
});
}
}
16 changes: 0 additions & 16 deletions packages/node/src/utils/generate-package-json.ts

This file was deleted.

57 changes: 48 additions & 9 deletions 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');
Expand All @@ -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',
Expand All @@ -28,26 +42,26 @@ 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);
});
});

describe('the optimization option when true', () => {
it('should minify', () => {
const result = getNodeWebpackConfig({
const result = getNodeWebpackConfig(context, projectGraph, {
...input,
optimization: true,
});
Expand All @@ -57,7 +71,7 @@ describe('getNodePartial', () => {
});

it('should concatenate modules', () => {
const result = getNodeWebpackConfig({
const result = getNodeWebpackConfig(context, projectGraph, {
...input,
optimization: true,
});
Expand All @@ -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'],
});
Expand All @@ -87,12 +101,37 @@ describe('getNodePartial', () => {
});

it('should not change any modules to commonjs imports', () => {
const result = getNodeWebpackConfig({
const result = getNodeWebpackConfig(context, projectGraph, {
...input,
externalDependencies: 'none',
});

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();
});
});
});
28 changes: 24 additions & 4 deletions 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',
Expand Down Expand Up @@ -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),
]);
}

0 comments on commit 7418ae1

Please sign in to comment.