From d31eda0566d85a63391e38f73aba288d2486fdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Tue, 18 Jun 2024 11:18:01 +0200 Subject: [PATCH] feat(webpack): add createNodesV2 for plugin --- packages/webpack/plugin.ts | 6 +- .../plugins/__snapshots__/plugin.spec.ts.snap | 112 +++++------ packages/webpack/src/plugins/plugin.spec.ts | 6 +- packages/webpack/src/plugins/plugin.ts | 178 +++++++++++------- 4 files changed, 177 insertions(+), 125 deletions(-) diff --git a/packages/webpack/plugin.ts b/packages/webpack/plugin.ts index f8ca64185f7ae..2bf2f581fadba 100644 --- a/packages/webpack/plugin.ts +++ b/packages/webpack/plugin.ts @@ -1 +1,5 @@ -export { createNodes } from './src/plugins/plugin'; +export { + createNodes, + createNodesV2, + type WebpackPluginOptions, +} from './src/plugins/plugin'; diff --git a/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap b/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap index e979f248205f9..0915efc9ae0d5 100644 --- a/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap +++ b/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap @@ -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, + }, + }, }, }, }, }, - }, -} + ], +] `; diff --git a/packages/webpack/src/plugins/plugin.spec.ts b/packages/webpack/src/plugins/plugin.spec.ts index d198a943581ea..9d944036be618 100644 --- a/packages/webpack/src/plugins/plugin.spec.ts +++ b/packages/webpack/src/plugins/plugin.spec.ts @@ -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; @@ -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', diff --git a/packages/webpack/src/plugins/plugin.ts b/packages/webpack/src/plugins/plugin.ts index cb4a375e4cec9..6cefc2642e3e6 100644 --- a/packages/webpack/src/plugins/plugin.ts +++ b/packages/webpack/src/plugins/plugin.ts @@ -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; @@ -26,86 +30,113 @@ export interface WebpackPluginOptions { previewTargetName?: string; } -const cachePath = join(workspaceDataDirectory, 'webpack.hash'); -const targetsCache = readTargetsCache(); +type WebpackTargets = Pick; -function readTargetsCache(): Record< - string, - Record -> { +function readTargetsCache(cachePath: string): Record { return existsSync(cachePath) ? readJsonFile(cachePath) : {}; } -function writeTargetsToCache() { - const oldCache = readTargetsCache(); - writeJsonFile(cachePath, { - ...oldCache, - ...targetsCache, - }); +function writeTargetsToCache( + cachePath: string, + results?: Record +) { + 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 = [ - '**/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 = [ + 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 = [ + 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, + context: CreateNodesContext, + targetsCache: Record +): Promise { + 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, context: CreateNodesContext -): Promise< - Record< - string, - TargetConfiguration - > -> { +): Promise { const namedInputs = getNamedInputs(projectRoot, context); const webpackConfig = resolveUserDefinedWebpackConfig( @@ -121,7 +152,7 @@ async function createWebpackTargets( projectRoot ); - const targets = {}; + const targets: Record = {}; targets[options.buildTargetName] = { command: `webpack-cli build`, @@ -171,7 +202,7 @@ async function createWebpackTargets( }, }; - return targets; + return { targets, metadata: {} }; } function normalizeOutputPath( @@ -204,3 +235,14 @@ function normalizeOutputPath( } } } + +function normalizeOptions( + options: WebpackPluginOptions +): Required { + return { + buildTargetName: options.buildTargetName ?? 'build', + serveTargetName: options.serveTargetName ?? 'serve', + serveStaticTargetName: options.serveStaticTargetName ?? 'serve-static', + previewTargetName: options.previewTargetName ?? 'preview', + }; +}