Skip to content

Commit

Permalink
feat(webpack): add createNodesV2 for plugin (#26588)
Browse files Browse the repository at this point in the history
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

There is no implementation for `createNodesV2`.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

There should be an implementation for `createNodesV2`.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
leosvelperez authored Jun 18, 2024
1 parent df3c752 commit 95a6e68
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 125 deletions.
6 changes: 5 additions & 1 deletion packages/webpack/plugin.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { createNodes } from './src/plugins/plugin';
export {
createNodes,
createNodesV2,
type WebpackPluginOptions,
} from './src/plugins/plugin';
112 changes: 59 additions & 53 deletions packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,63 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`@nx/webpack/plugin should create nodes 1`] = `
{
"projects": {
"my-app": {
"projectType": "application",
"targets": {
"build-something": {
"cache": true,
"command": "webpack-cli build",
"dependsOn": [
"^build-something",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"webpack-cli",
[
[
"my-app/webpack.config.js",
{
"projects": {
"my-app": {
"metadata": {},
"projectType": "application",
"targets": {
"build-something": {
"cache": true,
"command": "webpack-cli build",
"dependsOn": [
"^build-something",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"webpack-cli",
],
},
],
"options": {
"args": [
"--node-env=production",
],
"cwd": "my-app",
},
"outputs": [
"{projectRoot}/dist/foo",
],
},
],
"options": {
"args": [
"--node-env=production",
],
"cwd": "my-app",
},
"outputs": [
"{projectRoot}/dist/foo",
],
},
"my-serve": {
"command": "webpack-cli serve",
"options": {
"args": [
"--node-env=development",
],
"cwd": "my-app",
},
},
"preview-site": {
"command": "webpack-cli serve",
"options": {
"args": [
"--node-env=production",
],
"cwd": "my-app",
},
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "build-something",
"spa": true,
"my-serve": {
"command": "webpack-cli serve",
"options": {
"args": [
"--node-env=development",
],
"cwd": "my-app",
},
},
"preview-site": {
"command": "webpack-cli serve",
"options": {
"args": [
"--node-env=production",
],
"cwd": "my-app",
},
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "build-something",
"spa": true,
},
},
},
},
},
},
},
}
],
]
`;
6 changes: 3 additions & 3 deletions packages/webpack/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { CreateNodesContext } from '@nx/devkit';
import { createNodes } from './plugin';
import { createNodesV2 } from './plugin';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { join } from 'path';

describe('@nx/webpack/plugin', () => {
let createNodesFunction = createNodes[1];
let createNodesFunction = createNodesV2[1];
let context: CreateNodesContext;
let tempFs: TempFs;

Expand Down Expand Up @@ -40,7 +40,7 @@ describe('@nx/webpack/plugin', () => {
},
});
const nodes = await createNodesFunction(
'my-app/webpack.config.js',
['my-app/webpack.config.js'],
{
buildTargetName: 'build-something',
serveTargetName: 'my-serve',
Expand Down
178 changes: 110 additions & 68 deletions packages/webpack/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
createNodesFromFiles,
CreateNodesResult,
CreateNodesV2,
detectPackageManager,
logger,
ProjectConfiguration,
readJsonFile,
TargetConfiguration,
workspaceRoot,
writeJsonFile,
} from '@nx/devkit';
import { dirname, isAbsolute, join, relative, resolve } from 'path';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { WebpackExecutorOptions } from '../executors/webpack/schema';
import { WebDevServerOptions } from '../executors/dev-server/schema';
import { getLockFileName, getRootTsConfigPath } from '@nx/js';
import { existsSync, readdirSync } from 'fs';
import { hashObject } from 'nx/src/hasher/file-hasher';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { dirname, isAbsolute, join, relative, resolve } from 'path';
import { readWebpackOptions } from '../utils/webpack/read-webpack-options';
import { resolveUserDefinedWebpackConfig } from '../utils/webpack/resolve-user-defined-webpack-config';
import { getLockFileName, getRootTsConfigPath } from '@nx/js';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';

export interface WebpackPluginOptions {
buildTargetName?: string;
Expand All @@ -26,86 +30,113 @@ export interface WebpackPluginOptions {
previewTargetName?: string;
}

const cachePath = join(workspaceDataDirectory, 'webpack.hash');
const targetsCache = readTargetsCache();
type WebpackTargets = Pick<ProjectConfiguration, 'targets' | 'metadata'>;

function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration>
> {
function readTargetsCache(cachePath: string): Record<string, WebpackTargets> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
}

function writeTargetsToCache() {
const oldCache = readTargetsCache();
writeJsonFile(cachePath, {
...oldCache,
...targetsCache,
});
function writeTargetsToCache(
cachePath: string,
results?: Record<string, WebpackTargets>
) {
writeJsonFile(cachePath, results);
}

/**
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
*/
export const createDependencies: CreateDependencies = () => {
writeTargetsToCache();
return [];
};

export const createNodes: CreateNodes<WebpackPluginOptions> = [
'**/webpack.config.{js,ts,mjs,cjs}',
async (configFilePath, options, context) => {
options ??= {};
options.buildTargetName ??= 'build';
options.serveTargetName ??= 'serve';
options.serveStaticTargetName ??= 'serve-static';
options.previewTargetName ??= 'preview';

const projectRoot = dirname(configFilePath);

// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
const webpackConfigGlob = '**/webpack.config.{js,ts,mjs,cjs}';

export const createNodesV2: CreateNodesV2<WebpackPluginOptions> = [
webpackConfigGlob,
async (configFilePaths, options, context) => {
const optionsHash = hashObject(options);
const cachePath = join(
workspaceDataDirectory,
`webpack-${optionsHash}.hash`
);
const targetsCache = readTargetsCache(cachePath);
const normalizedOptions = normalizeOptions(options);
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFilePaths,
normalizedOptions,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];

const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
export const createNodes: CreateNodes<WebpackPluginOptions> = [
webpackConfigGlob,
async (configFilePath, options, context) => {
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
const targets = targetsCache[hash]
? targetsCache[hash]
: await createWebpackTargets(
configFilePath,
projectRoot,
options,
context
);

return {
projects: {
[projectRoot]: {
projectType: 'application',
targets,
},
},
};
const normalizedOptions = normalizeOptions(options);
return createNodesInternal(configFilePath, normalizedOptions, context, {});
},
];

async function createNodesInternal(
configFilePath: string,
options: Required<WebpackPluginOptions>,
context: CreateNodesContext,
targetsCache: Record<string, WebpackTargets>
): Promise<CreateNodesResult> {
const projectRoot = dirname(configFilePath);

// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}

const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);

targetsCache[hash] ??= await createWebpackTargets(
configFilePath,
projectRoot,
options,
context
);

const { targets, metadata } = targetsCache[hash];

return {
projects: {
[projectRoot]: {
projectType: 'application',
targets,
metadata,
},
},
};
}

async function createWebpackTargets(
configFilePath: string,
projectRoot: string,
options: WebpackPluginOptions,
options: Required<WebpackPluginOptions>,
context: CreateNodesContext
): Promise<
Record<
string,
TargetConfiguration<WebpackExecutorOptions | WebDevServerOptions>
>
> {
): Promise<WebpackTargets> {
const namedInputs = getNamedInputs(projectRoot, context);

const webpackConfig = resolveUserDefinedWebpackConfig(
Expand All @@ -121,7 +152,7 @@ async function createWebpackTargets(
projectRoot
);

const targets = {};
const targets: Record<string, TargetConfiguration> = {};

targets[options.buildTargetName] = {
command: `webpack-cli build`,
Expand Down Expand Up @@ -171,7 +202,7 @@ async function createWebpackTargets(
},
};

return targets;
return { targets, metadata: {} };
}

function normalizeOutputPath(
Expand Down Expand Up @@ -204,3 +235,14 @@ function normalizeOutputPath(
}
}
}

function normalizeOptions(
options: WebpackPluginOptions
): Required<WebpackPluginOptions> {
return {
buildTargetName: options.buildTargetName ?? 'build',
serveTargetName: options.serveTargetName ?? 'serve',
serveStaticTargetName: options.serveStaticTargetName ?? 'serve-static',
previewTargetName: options.previewTargetName ?? 'preview',
};
}

0 comments on commit 95a6e68

Please sign in to comment.