From c1b31802fc4baf7ed890ddc5fa6bb82d06a2ae77 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Wed, 8 Nov 2023 11:44:19 -0500 Subject: [PATCH] feat(webpack): add plugin to automatically configure build and serve targets for projects --- .../packages/webpack/executors/webpack.json | 76 ++---- .../react/plugins/component-testing/index.ts | 6 +- .../lib/apply-react-config.ts | 4 +- .../with-module-federation.ts | 6 +- .../web/src/executors/file-server/schema.d.ts | 6 +- packages/webpack/plugin.ts | 1 + .../executors/dev-server/dev-server.impl.ts | 25 +- .../dev-server/lib/get-dev-server-config.ts | 9 +- .../src/executors/dev-server/schema.d.ts | 16 +- .../webpack/lib/normalize-options.ts | 2 +- .../webpack/src/executors/webpack/schema.d.ts | 2 + .../webpack/src/executors/webpack/schema.json | 73 ++---- .../src/executors/webpack/webpack.impl.ts | 24 +- packages/webpack/src/generators/init/init.ts | 30 +++ .../lib/apply-base-config.ts | 32 ++- .../nx-webpack-plugin/lib/apply-web-config.ts | 13 + .../nx-webpack-plugin-options.ts | 2 +- .../nx-webpack-plugin/nx-webpack-plugin.ts | 22 +- packages/webpack/src/plugins/plugin.ts | 241 ++++++++++++++++++ packages/webpack/src/utils/config.spec.ts | 3 + packages/webpack/src/utils/config.ts | 76 +++--- ...=> resolve-user-defined-webpack-config.ts} | 12 +- packages/webpack/src/utils/with-nx.ts | 61 +++-- packages/webpack/src/utils/with-web.ts | 65 +++-- 24 files changed, 543 insertions(+), 264 deletions(-) create mode 100644 packages/webpack/plugin.ts create mode 100644 packages/webpack/src/plugins/plugin.ts rename packages/webpack/src/utils/webpack/{custom-webpack.ts => resolve-user-defined-webpack-config.ts} (83%) diff --git a/docs/generated/packages/webpack/executors/webpack.json b/docs/generated/packages/webpack/executors/webpack.json index 7b5b12add6c9f5..dcd3faaa3c3135 100644 --- a/docs/generated/packages/webpack/executors/webpack.json +++ b/docs/generated/packages/webpack/executors/webpack.json @@ -30,8 +30,7 @@ "compiler": { "type": "string", "description": "The compiler to use.", - "enum": ["babel", "swc", "tsc"], - "default": "babel" + "enum": ["babel", "swc", "tsc"] }, "outputPath": { "type": "string", @@ -43,8 +42,7 @@ "type": "string", "alias": "platform", "description": "Target platform for the build, same as the Webpack target option.", - "enum": ["node", "web", "webworker"], - "default": "web" + "enum": ["node", "web", "webworker"] }, "deleteOutputPath": { "type": "boolean", @@ -53,8 +51,7 @@ }, "watch": { "type": "boolean", - "description": "Enable re-building when files change.", - "default": false + "description": "Enable re-building when files change." }, "baseHref": { "type": "string", @@ -66,33 +63,27 @@ }, "vendorChunk": { "type": "boolean", - "description": "Use a separate bundle containing only vendor libraries.", - "default": true + "description": "Use a separate bundle containing only vendor libraries." }, "commonChunk": { "type": "boolean", - "description": "Use a separate bundle containing code used across multiple bundles.", - "default": true + "description": "Use a separate bundle containing code used across multiple bundles." }, "runtimeChunk": { "type": "boolean", - "description": "Use a separate bundle containing the runtime.", - "default": true + "description": "Use a separate bundle containing the runtime." }, "sourceMap": { "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.", - "default": true, "oneOf": [{ "type": "boolean" }, { "type": "string" }] }, "progress": { "type": "boolean", - "description": "Log progress to the console while building.", - "default": false + "description": "Log progress to the console while building." }, "assets": { "type": "array", "description": "List of static application assets.", - "default": [], "items": { "oneOf": [ { @@ -163,8 +154,7 @@ "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" } ] - }, - "default": [] + } }, "styles": { "type": "array", @@ -200,18 +190,15 @@ "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" } ] - }, - "default": [] + } }, "namedChunks": { "type": "boolean", - "description": "Names the produced bundles according to their entry file.", - "default": true + "description": "Names the produced bundles according to their entry file." }, "outputHashing": { "type": "string", "description": "Define the output filename cache-busting hashing mode.", - "default": "none", "enum": ["none", "all", "media", "bundles"] }, "stylePreprocessorOptions": { @@ -221,8 +208,7 @@ "includePaths": { "description": "Paths to include. Paths will be resolved to project root.", "type": "array", - "items": { "type": "string" }, - "default": [] + "items": { "type": "string" } } }, "additionalProperties": false @@ -251,13 +237,11 @@ }, "generatePackageJson": { "type": "boolean", - "description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated.", - "default": false + "description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated." }, "transformers": { "type": "array", "description": "List of TypeScript Compiler Transfomers Plugins.", - "default": [], "aliases": ["tsPlugins"], "items": { "oneOf": [ @@ -302,18 +286,15 @@ { "type": "string", "enum": ["none", "all"] }, { "type": "array", "items": { "type": "string" } } ], - "description": "Dependencies to keep external to the bundle. (`all` (default), `none`, or an array of module names)", - "default": "all" + "description": "Dependencies to keep external to the bundle. (`all` (default), `none`, or an array of module names)" }, "extractCss": { "type": "boolean", - "description": "Extract CSS into a `.css` file.", - "default": true + "description": "Extract CSS into a `.css` file." }, "subresourceIntegrity": { "type": "boolean", - "description": "Enables the use of subresource integrity validation.", - "default": false + "description": "Enables the use of subresource integrity validation." }, "polyfills": { "type": "string", @@ -321,30 +302,24 @@ "x-completion-type": "file", "x-completion-glob": "**/*@(.js|.ts|.tsx)" }, - "verbose": { - "type": "boolean", - "description": "Emits verbose output", - "default": false - }, + "verbose": { "type": "boolean", "description": "Emits verbose output" }, "statsJson": { "type": "boolean", - "description": "Generates a 'stats.json' file which can be analyzed using tools such as: 'webpack-bundle-analyzer' or ``.", - "default": false + "description": "Generates a 'stats.json' file which can be analyzed using tools such as: 'webpack-bundle-analyzer' or ``." }, "isolatedConfig": { "type": "boolean", "description": "Do not apply Nx webpack plugins automatically. Plugins need to be applied in the project's webpack.config.js file (e.g. withNx, withReact, etc.).", - "default": false + "default": true, + "x-deprecated": "Automatic configuration of Webpack is deprecated in favor of an explicit 'webpack.config.js' file. This option will be removed in Nx 18. See https://nx.dev/recipes/webpack/webpack-config-setup." }, "extractLicenses": { "type": "boolean", - "description": "Extract all licenses in a separate file, in the case of production builds only.", - "default": false + "description": "Extract all licenses in a separate file, in the case of production builds only." }, "memoryLimit": { "type": "number", - "description": "Memory limit for type checking service process in `MB`.", - "default": 2048 + "description": "Memory limit for type checking service process in `MB`." }, "fileReplacements": { "description": "Replace files with other files in the build.", @@ -365,8 +340,7 @@ }, "additionalProperties": false, "required": ["replace", "with"] - }, - "default": [] + } }, "buildLibsFromSource": { "type": "boolean", @@ -375,8 +349,7 @@ }, "generateIndexHtml": { "type": "boolean", - "description": "Generates `index.html` file to the output path. This can be turned off if using a webpack plugin to generate HTML such as `html-webpack-plugin`.", - "default": true + "description": "Generates `index.html` file to the output path. This can be turned off if using a webpack plugin to generate HTML such as `html-webpack-plugin`." }, "postcssConfig": { "type": "string", @@ -391,8 +364,7 @@ }, "babelUpwardRootMode": { "type": "boolean", - "description": "Whether to set rootmode to upward. See https://babeljs.io/docs/en/options#rootmode", - "default": false + "description": "Whether to set rootmode to upward. See https://babeljs.io/docs/en/options#rootmode" }, "babelConfig": { "type": "string", diff --git a/packages/react/plugins/component-testing/index.ts b/packages/react/plugins/component-testing/index.ts index 7cde7d951cb505..b88b1425c3dee2 100644 --- a/packages/react/plugins/component-testing/index.ts +++ b/packages/react/plugins/component-testing/index.ts @@ -235,8 +235,8 @@ function buildTargetWebpack( normalizeOptions, } = require('@nx/webpack/src/executors/webpack/lib/normalize-options'); const { - resolveCustomWebpackConfig, - } = require('@nx/webpack/src/utils/webpack/custom-webpack'); + resolveUserDefinedWebpackConfig, + } = require('@nx/webpack/src/utils/webpack/resolve-user-defined-webpack-config'); const { getWebpackConfig, } = require('@nx/webpack/src/executors/webpack/lib/get-webpack-config'); @@ -251,7 +251,7 @@ function buildTargetWebpack( let customWebpack: any; if (options.webpackConfig) { - customWebpack = resolveCustomWebpackConfig( + customWebpack = resolveUserDefinedWebpackConfig( options.webpackConfig, options.tsConfig.startsWith(context.root) ? options.tsConfig diff --git a/packages/react/plugins/nx-react-webpack-plugin/lib/apply-react-config.ts b/packages/react/plugins/nx-react-webpack-plugin/lib/apply-react-config.ts index 890fd143c95e12..a6bc7336979e1d 100644 --- a/packages/react/plugins/nx-react-webpack-plugin/lib/apply-react-config.ts +++ b/packages/react/plugins/nx-react-webpack-plugin/lib/apply-react-config.ts @@ -1,9 +1,11 @@ -import { Compiler, Configuration, WebpackOptionsNormalized } from 'webpack'; +import { Configuration, WebpackOptionsNormalized } from 'webpack'; export function applyReactConfig( options: { svgr?: boolean }, config: Partial = {} ): void { + if (!process.env['NX_TASK_TARGET_PROJECT']) return; + addHotReload(config); if (options.svgr !== false) { diff --git a/packages/react/src/module-federation/with-module-federation.ts b/packages/react/src/module-federation/with-module-federation.ts index 6c705cfe0a9869..8cd4dceab4f504 100644 --- a/packages/react/src/module-federation/with-module-federation.ts +++ b/packages/react/src/module-federation/with-module-federation.ts @@ -1,15 +1,15 @@ import { ModuleFederationConfig } from '@nx/webpack/src/utils/module-federation'; import { getModuleFederationConfig } from './utils'; -import type { AsyncNxWebpackPlugin } from '@nx/webpack'; +import type { AsyncNxComposableWebpackPlugin } from '@nx/webpack'; import ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); /** * @param {ModuleFederationConfig} options - * @return {Promise} + * @return {Promise} */ export async function withModuleFederation( options: ModuleFederationConfig -): Promise { +): Promise { const { sharedDependencies, sharedLibraries, mappedRemotes } = await getModuleFederationConfig(options); diff --git a/packages/web/src/executors/file-server/schema.d.ts b/packages/web/src/executors/file-server/schema.d.ts index 2d901fc7cd2003..d095e23c08dcf2 100644 --- a/packages/web/src/executors/file-server/schema.d.ts +++ b/packages/web/src/executors/file-server/schema.d.ts @@ -1,7 +1,7 @@ export interface Schema { - host: string; - port: number; - ssl: boolean; + host?: string; + port?: number; + ssl?: boolean; sslKey?: string; sslCert?: string; proxyUrl?: string; diff --git a/packages/webpack/plugin.ts b/packages/webpack/plugin.ts new file mode 100644 index 00000000000000..f8ca64185f7ae4 --- /dev/null +++ b/packages/webpack/plugin.ts @@ -0,0 +1 @@ +export { createNodes } from './src/plugins/plugin'; diff --git a/packages/webpack/src/executors/dev-server/dev-server.impl.ts b/packages/webpack/src/executors/dev-server/dev-server.impl.ts index 3774795688eeae..afb03862fe731b 100644 --- a/packages/webpack/src/executors/dev-server/dev-server.impl.ts +++ b/packages/webpack/src/executors/dev-server/dev-server.impl.ts @@ -15,11 +15,12 @@ import { createTmpTsConfig, } from '@nx/js/src/utils/buildable-libs-utils'; import { runWebpackDevServer } from '../../utils/run-webpack'; -import { resolveCustomWebpackConfig } from '../../utils/webpack/custom-webpack'; +import { resolveUserDefinedWebpackConfig } from '../../utils/webpack/resolve-user-defined-webpack-config'; import { normalizeOptions } from '../webpack/lib/normalize-options'; import { WebpackExecutorOptions } from '../webpack/schema'; import { WebDevServerOptions } from './schema'; import { join } from 'path'; +import { isNxWebpackComposablePlugin } from '../../utils/config'; export async function* devServerExecutor( serveOptions: WebDevServerOptions, @@ -37,12 +38,6 @@ export async function* devServerExecutor( sourceRoot ); - if (!buildOptions.index) { - throw new Error( - `Cannot run dev-server without "index" option. Check the build options for ${context.projectName}.` - ); - } - if (!buildOptions.buildLibsFromSource) { const { target, dependencies } = calculateProjectBuildableDependencies( context.taskGraph, @@ -66,30 +61,30 @@ export async function* devServerExecutor( let tsconfigPath = buildOptions.tsConfig.startsWith(context.root) ? buildOptions.tsConfig : join(context.root, buildOptions.tsConfig); - let customWebpack = resolveCustomWebpackConfig( + let userDefinedWebpackConfig = resolveUserDefinedWebpackConfig( buildOptions.webpackConfig, tsconfigPath ); - if (typeof customWebpack.then === 'function') { - customWebpack = await customWebpack; + if (typeof userDefinedWebpackConfig.then === 'function') { + userDefinedWebpackConfig = await userDefinedWebpackConfig; } - if (typeof customWebpack === 'function') { + if (isNxWebpackComposablePlugin(userDefinedWebpackConfig)) { // Old behavior, call the webpack function that is specific to Nx - config = await customWebpack(config, { + config = await userDefinedWebpackConfig(config, { options: buildOptions, context, configuration: serveOptions.buildTarget.split(':')[2], }); - } else if (customWebpack) { + } else if (userDefinedWebpackConfig) { // New behavior, use the config object as is with devServer defaults config = { devServer: { - ...customWebpack.devServer, + ...userDefinedWebpackConfig.devServer, ...config.devServer, }, - ...customWebpack, + ...userDefinedWebpackConfig, }; } } diff --git a/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts b/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts index c86b2fb135ae68..77475a6b76d6a8 100644 --- a/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts +++ b/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts @@ -47,11 +47,14 @@ function getDevServerPartial( } const config: WebpackDevServerConfiguration = { - host: options.host, - port: options.port, + // TODO(wip): These should be read from actual config + host: options.host ?? 'localhost', + port: options.port ?? 4200, headers: { 'Access-Control-Allow-Origin': '*' }, historyApiFallback: { - index: `${servePath}${path.basename(buildOptions.index)}`, + index: + buildOptions.index && + `${servePath}${path.basename(buildOptions.index)}`, disableDotRule: true, htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], }, diff --git a/packages/webpack/src/executors/dev-server/schema.d.ts b/packages/webpack/src/executors/dev-server/schema.d.ts index b7cac5d605c62d..e556aa4dff2295 100644 --- a/packages/webpack/src/executors/dev-server/schema.d.ts +++ b/packages/webpack/src/executors/dev-server/schema.d.ts @@ -1,17 +1,17 @@ export interface WebDevServerOptions { - host: string; - port: number; + host?: string; + port?: number; publicHost?: string; - ssl: boolean; + ssl?: boolean; sslKey?: string; sslCert?: string; proxyConfig?: string; buildTarget: string; - open: boolean; - liveReload: boolean; - hmr: boolean; - watch: boolean; - allowedHosts: string; + open?: boolean; + liveReload?: boolean; + hmr?: boolean; + watch?: boolean; + allowedHosts?: string; memoryLimit?: number; baseHref?: string; } diff --git a/packages/webpack/src/executors/webpack/lib/normalize-options.ts b/packages/webpack/src/executors/webpack/lib/normalize-options.ts index 6106185a0d17c3..42febc04253b8b 100644 --- a/packages/webpack/src/executors/webpack/lib/normalize-options.ts +++ b/packages/webpack/src/executors/webpack/lib/normalize-options.ts @@ -19,7 +19,7 @@ export function normalizeOptions( sourceRoot, target: options.target ?? 'web', outputFileName: options.outputFileName ?? 'main.js', - assets: normalizeAssets(options.assets, root, sourceRoot), + assets: options.assets && normalizeAssets(options.assets, root, sourceRoot), webpackConfig: normalizePluginPath(options.webpackConfig, root), optimization: typeof options.optimization !== 'object' diff --git a/packages/webpack/src/executors/webpack/schema.d.ts b/packages/webpack/src/executors/webpack/schema.d.ts index 22dd3c25bb13d6..59618bf1a9ef44 100644 --- a/packages/webpack/src/executors/webpack/schema.d.ts +++ b/packages/webpack/src/executors/webpack/schema.d.ts @@ -47,6 +47,8 @@ export interface WebpackExecutorOptions { extractLicenses?: boolean; fileReplacements?: FileReplacement[]; generatePackageJson?: boolean; + // TODO(v18): Remove this option + /** @deprecated set webpackConfig and provide an explicit webpack.config.js file (See: https://nx.dev/recipes/webpack/webpack-config-setup) */ isolatedConfig?: boolean; main: string; memoryLimit?: number; diff --git a/packages/webpack/src/executors/webpack/schema.json b/packages/webpack/src/executors/webpack/schema.json index befe0e7e5960f6..e5ce6e9d5c260f 100644 --- a/packages/webpack/src/executors/webpack/schema.json +++ b/packages/webpack/src/executors/webpack/schema.json @@ -27,8 +27,7 @@ "compiler": { "type": "string", "description": "The compiler to use.", - "enum": ["babel", "swc", "tsc"], - "default": "babel" + "enum": ["babel", "swc", "tsc"] }, "outputPath": { "type": "string", @@ -40,8 +39,7 @@ "type": "string", "alias": "platform", "description": "Target platform for the build, same as the Webpack target option.", - "enum": ["node", "web", "webworker"], - "default": "web" + "enum": ["node", "web", "webworker"] }, "deleteOutputPath": { "type": "boolean", @@ -50,8 +48,7 @@ }, "watch": { "type": "boolean", - "description": "Enable re-building when files change.", - "default": false + "description": "Enable re-building when files change." }, "baseHref": { "type": "string", @@ -63,22 +60,18 @@ }, "vendorChunk": { "type": "boolean", - "description": "Use a separate bundle containing only vendor libraries.", - "default": true + "description": "Use a separate bundle containing only vendor libraries." }, "commonChunk": { "type": "boolean", - "description": "Use a separate bundle containing code used across multiple bundles.", - "default": true + "description": "Use a separate bundle containing code used across multiple bundles." }, "runtimeChunk": { "type": "boolean", - "description": "Use a separate bundle containing the runtime.", - "default": true + "description": "Use a separate bundle containing the runtime." }, "sourceMap": { "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.", - "default": true, "oneOf": [ { "type": "boolean" @@ -90,13 +83,11 @@ }, "progress": { "type": "boolean", - "description": "Log progress to the console while building.", - "default": false + "description": "Log progress to the console while building." }, "assets": { "type": "array", "description": "List of static application assets.", - "default": [], "items": { "$ref": "#/definitions/assetPattern" } @@ -112,26 +103,22 @@ "description": "External Scripts which will be included before the main application entry.", "items": { "$ref": "#/definitions/extraEntryPoint" - }, - "default": [] + } }, "styles": { "type": "array", "description": "External Styles which will be included with the application", "items": { "$ref": "#/definitions/extraEntryPoint" - }, - "default": [] + } }, "namedChunks": { "type": "boolean", - "description": "Names the produced bundles according to their entry file.", - "default": true + "description": "Names the produced bundles according to their entry file." }, "outputHashing": { "type": "string", "description": "Define the output filename cache-busting hashing mode.", - "default": "none", "enum": ["none", "all", "media", "bundles"] }, "stylePreprocessorOptions": { @@ -143,8 +130,7 @@ "type": "array", "items": { "type": "string" - }, - "default": [] + } } }, "additionalProperties": false @@ -175,13 +161,11 @@ }, "generatePackageJson": { "type": "boolean", - "description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated.", - "default": false + "description": "Generates a `package.json` and pruned lock file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated." }, "transformers": { "type": "array", "description": "List of TypeScript Compiler Transfomers Plugins.", - "default": [], "aliases": ["tsPlugins"], "items": { "$ref": "#/definitions/transformerPattern" @@ -223,18 +207,15 @@ } } ], - "description": "Dependencies to keep external to the bundle. (`all` (default), `none`, or an array of module names)", - "default": "all" + "description": "Dependencies to keep external to the bundle. (`all` (default), `none`, or an array of module names)" }, "extractCss": { "type": "boolean", - "description": "Extract CSS into a `.css` file.", - "default": true + "description": "Extract CSS into a `.css` file." }, "subresourceIntegrity": { "type": "boolean", - "description": "Enables the use of subresource integrity validation.", - "default": false + "description": "Enables the use of subresource integrity validation." }, "polyfills": { "type": "string", @@ -244,28 +225,25 @@ }, "verbose": { "type": "boolean", - "description": "Emits verbose output", - "default": false + "description": "Emits verbose output" }, "statsJson": { "type": "boolean", - "description": "Generates a 'stats.json' file which can be analyzed using tools such as: 'webpack-bundle-analyzer' or ``.", - "default": false + "description": "Generates a 'stats.json' file which can be analyzed using tools such as: 'webpack-bundle-analyzer' or ``." }, "isolatedConfig": { "type": "boolean", "description": "Do not apply Nx webpack plugins automatically. Plugins need to be applied in the project's webpack.config.js file (e.g. withNx, withReact, etc.).", - "default": false + "default": true, + "x-deprecated": "Automatic configuration of Webpack is deprecated in favor of an explicit 'webpack.config.js' file. This option will be removed in Nx 18. See https://nx.dev/recipes/webpack/webpack-config-setup." }, "extractLicenses": { "type": "boolean", - "description": "Extract all licenses in a separate file, in the case of production builds only.", - "default": false + "description": "Extract all licenses in a separate file, in the case of production builds only." }, "memoryLimit": { "type": "number", - "description": "Memory limit for type checking service process in `MB`.", - "default": 2048 + "description": "Memory limit for type checking service process in `MB`." }, "fileReplacements": { "description": "Replace files with other files in the build.", @@ -286,8 +264,7 @@ }, "additionalProperties": false, "required": ["replace", "with"] - }, - "default": [] + } }, "buildLibsFromSource": { "type": "boolean", @@ -296,8 +273,7 @@ }, "generateIndexHtml": { "type": "boolean", - "description": "Generates `index.html` file to the output path. This can be turned off if using a webpack plugin to generate HTML such as `html-webpack-plugin`.", - "default": true + "description": "Generates `index.html` file to the output path. This can be turned off if using a webpack plugin to generate HTML such as `html-webpack-plugin`." }, "postcssConfig": { "type": "string", @@ -312,8 +288,7 @@ }, "babelUpwardRootMode": { "type": "boolean", - "description": "Whether to set rootmode to upward. See https://babeljs.io/docs/en/options#rootmode", - "default": false + "description": "Whether to set rootmode to upward. See https://babeljs.io/docs/en/options#rootmode" }, "babelConfig": { "type": "string", diff --git a/packages/webpack/src/executors/webpack/webpack.impl.ts b/packages/webpack/src/executors/webpack/webpack.impl.ts index 4bd99f334887d9..ed96c28d9ca9cb 100644 --- a/packages/webpack/src/executors/webpack/webpack.impl.ts +++ b/packages/webpack/src/executors/webpack/webpack.impl.ts @@ -18,16 +18,16 @@ import { import { getWebpackConfig } from './lib/get-webpack-config'; import { runWebpack } from './lib/run-webpack'; import { deleteOutputDir } from '../../utils/fs'; -import { resolveCustomWebpackConfig } from '../../utils/webpack/custom-webpack'; +import { resolveUserDefinedWebpackConfig } from '../../utils/webpack/resolve-user-defined-webpack-config'; import type { NormalizedWebpackExecutorOptions, WebpackExecutorOptions, } from './schema'; import { normalizeOptions } from './lib/normalize-options'; +import { isNxWebpackComposablePlugin } from '../../utils/config'; async function getWebpackConfigs( options: NormalizedWebpackExecutorOptions, - projectRoot: string, context: ExecutorContext ): Promise { if (options.isolatedConfig && !options.webpackConfig) { @@ -36,17 +36,17 @@ async function getWebpackConfigs( ); } - let customWebpack = null; + let userDefinedWebpackConfig = null; if (options.webpackConfig && options.tsConfig) { - customWebpack = resolveCustomWebpackConfig( + userDefinedWebpackConfig = resolveUserDefinedWebpackConfig( options.webpackConfig, options.tsConfig.startsWith(context.root) ? options.tsConfig : join(context.root, options.tsConfig) ); - if (typeof customWebpack.then === 'function') { - customWebpack = await customWebpack; + if (typeof userDefinedWebpackConfig.then === 'function') { + userDefinedWebpackConfig = await userDefinedWebpackConfig; } } @@ -54,16 +54,16 @@ async function getWebpackConfigs( ? {} : getWebpackConfig(context, options); - if (typeof customWebpack === 'function') { + if (isNxWebpackComposablePlugin(userDefinedWebpackConfig)) { // Old behavior, call the Nx-specific webpack config function that user exports - return await customWebpack(config, { + return await userDefinedWebpackConfig(config, { options, context, configuration: context.configurationName, // backwards compat }); - } else if (customWebpack) { + } else if (userDefinedWebpackConfig) { // New behavior, we want the webpack config to export object - return customWebpack; + return userDefinedWebpackConfig; } else { // Fallback case, if we cannot find a webpack config path return config; @@ -86,6 +86,8 @@ export async function* webpackExecutor( _options: WebpackExecutorOptions, context: ExecutorContext ): AsyncGenerator { + // Default to production build. + process.env['NODE_ENV'] ||= 'production'; // Pass to NxWebpackPlugin so we can get the CLI overrides. process.env['NX_WEBPACK_EXECUTOR_RAW_OPTIONS'] = JSON.stringify(_options); @@ -157,7 +159,7 @@ export async function* webpackExecutor( ); } - const configs = await getWebpackConfigs(options, metadata.root, context); + const configs = await getWebpackConfigs(options, context); return yield* eachValueFrom( of(configs).pipe( diff --git a/packages/webpack/src/generators/init/init.ts b/packages/webpack/src/generators/init/init.ts index ea94976c026b68..ab8624810eae28 100644 --- a/packages/webpack/src/generators/init/init.ts +++ b/packages/webpack/src/generators/init/init.ts @@ -2,8 +2,10 @@ import { addDependenciesToPackageJson, formatFiles, GeneratorCallback, + readNxJson, runTasksInSerial, Tree, + updateNxJson, } from '@nx/devkit'; import { addSwcDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies'; @@ -18,8 +20,10 @@ import { urlLoaderVersion, } from '../../utils/versions'; import { addBabelInputs } from '@nx/js/src/utils/add-babel-inputs'; +import { WebpackPluginOptions } from '../../plugins/plugin'; export async function webpackInitGenerator(tree: Tree, schema: Schema) { + const addPlugins = process.env.NX_PCV3 === 'true'; const tasks: GeneratorCallback[] = []; const devDependencies = { '@nx/webpack': nxVersion, @@ -54,7 +58,33 @@ export async function webpackInitGenerator(tree: Tree, schema: Schema) { ); tasks.push(baseInstalTask); + if (addPlugins) addPlugin(tree); + return runTasksInSerial(...tasks); } +function addPlugin(tree: Tree) { + const nxJson = readNxJson(tree); + nxJson.plugins ??= []; + + for (const plugin of nxJson.plugins) { + if ( + typeof plugin === 'string' + ? plugin === '@nx/webpack/plugin' + : plugin.plugin === '@nx/webpack/plugin' + ) { + return; + } + } + + nxJson.plugins.push({ + plugin: '@nx/webpack/plugin', + options: { + buildTargetName: 'build', + serveTargetName: 'serve', + } as WebpackPluginOptions, + }); + updateNxJson(tree, nxJson); +} + export default webpackInitGenerator; diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts index eb29ce34bf0eb7..2ad34400718e9e 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts @@ -28,6 +28,10 @@ const IGNORED_WEBPACK_WARNINGS = [ const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx']; const mainFields = ['module', 'main']; +// Used by `@nx/webpack/plugin` to determine options such as main, tsConfig, etc. +export let applyBaseConfigOptionsFromLastInvocation: null | NormalizedNxWebpackPluginOptions = + null; + export function applyBaseConfig( options: NormalizedNxWebpackPluginOptions, config: Partial = {}, @@ -40,6 +44,18 @@ export function applyBaseConfig( useNormalizedEntry?: boolean; } = {} ): void { + applyBaseConfigOptionsFromLastInvocation = options; + if (!process.env['NX_TASK_TARGET_PROJECT']) return; + + // Defaults that was applied from executor schema previously. + options.compiler ??= 'babel'; + options.deleteOutputPath ??= true; + options.externalDependencies ??= 'all'; + options.fileReplacements ??= []; + options.memoryLimit ??= 2048; + options.transformers ??= []; + options.runtimeChunk ??= true; + const plugins: WebpackPluginInstance[] = [ new NxTsconfigPathsWebpackPlugin(options), ]; @@ -231,13 +247,15 @@ export function applyBaseConfig( config.resolve = { ...config.resolve, extensions: [...extensions, ...(config?.resolve?.extensions ?? [])], - alias: options.fileReplacements.reduce( - (aliases, replacement) => ({ - ...aliases, - [replacement.replace]: replacement.with, - }), - {} - ), + alias: + options.fileReplacements && + options.fileReplacements.reduce( + (aliases, replacement) => ({ + ...aliases, + [replacement.replace]: replacement.with, + }), + {} + ), mainFields, }; diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts index a3caf88baafa38..ad2a0f5d35817f 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts @@ -23,6 +23,10 @@ import { instantiateScriptPlugins } from './instantiate-script-plugins'; import CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); import MiniCssExtractPlugin = require('mini-css-extract-plugin'); +// Used by `@nx/webpack/plugin` to determine options such as main, tsConfig, etc. +export let applyWebConfigOptionsFromLastInvocation: null | NormalizedNxWebpackPluginOptions = + null; + export function applyWebConfig( options: NormalizedNxWebpackPluginOptions, config: Partial = {}, @@ -35,6 +39,15 @@ export function applyWebConfig( useNormalizedEntry?: boolean; } = {} ): void { + applyWebConfigOptionsFromLastInvocation = options; + if (!process.env['NX_TASK_TARGET_PROJECT']) return; + + // Defaults that was applied from executor schema previously. + options.extractCss ??= true; + options.generateIndexHtml ??= true; + options.styles ??= []; + options.scripts ??= []; + const plugins: WebpackPluginInstance[] = []; const stylesOptimization = diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin-options.ts b/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin-options.ts index 38b304b57992a2..6e0f2b9a159602 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin-options.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin-options.ts @@ -40,7 +40,6 @@ export interface OptimizationOptions { export interface NxWebpackPluginOptions { // Required options main: string; - outputPath: string; tsConfig: string; // Optional options @@ -66,6 +65,7 @@ export interface NxWebpackPluginOptions { optimization?: boolean | OptimizationOptions; outputFileName?: string; outputHashing?: any; + outputPath?: string; poll?: number; polyfills?: string; postcssConfig?: string; diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin.ts b/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin.ts index e8c5abbdeff520..2ecd3d8a35b7d1 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/nx-webpack-plugin.ts @@ -20,12 +20,18 @@ import { applyWebConfig } from './lib/apply-web-config'; */ export class NxWebpackPlugin { private readonly options: NormalizedNxWebpackPluginOptions; + static optionsFromLastInvocation: null | NxWebpackPluginOptions = null; constructor(options: NxWebpackPluginOptions) { - this.options = normalizeOptions({ - ...options, - ...this.readExecutorOptions(), - }); + NxWebpackPlugin.optionsFromLastInvocation = options; + + // If we're creating nodes, we're not building so skip. + if (process.env['NX_TASK_TARGET_PROJECT']) { + this.options = normalizeOptions({ + ...options, + ...this.readExecutorOptions(), + }); + } } apply(compiler: Compiler): void { @@ -35,10 +41,6 @@ export class NxWebpackPlugin { this.options.target = target; } - if (this.options.deleteOutputPath) { - deleteOutputDir(this.options.root, this.options.outputPath); - } - applyBaseConfig(this.options, compiler.options, { useNormalizedEntry: true, }); @@ -52,6 +54,10 @@ export class NxWebpackPlugin { useNormalizedEntry: true, }); } + + if (this.options.deleteOutputPath) { + deleteOutputDir(this.options.root, this.options.outputPath); + } } private readExecutorOptions() { diff --git a/packages/webpack/src/plugins/plugin.ts b/packages/webpack/src/plugins/plugin.ts new file mode 100644 index 00000000000000..171d289e8f4eba --- /dev/null +++ b/packages/webpack/src/plugins/plugin.ts @@ -0,0 +1,241 @@ +import { + CreateNodesAsync, + CreateNodesContext, + TargetConfiguration, + joinPathFragments, +} from '@nx/devkit'; +import { basename, dirname, join } from 'path'; +import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; +import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils'; +import { WebpackExecutorOptions } from '../executors/webpack/schema'; +import { WebDevServerOptions } from '../executors/dev-server/schema'; +import { getRootTsConfigPath } from '@nx/js'; +import { resolveUserDefinedWebpackConfig } from '../utils/webpack/resolve-user-defined-webpack-config'; +import { isNxWebpackComposablePlugin } from '../utils/config'; +import { applyBaseConfigOptionsFromLastInvocation } from './nx-webpack-plugin/lib/apply-base-config'; +import { existsSync, readdirSync } from 'fs'; +import { NxWebpackPlugin } from './nx-webpack-plugin/nx-webpack-plugin'; + +export interface WebpackPluginOptions { + buildTargetName?: string; + serveTargetName?: string; + mainFiles?: string[]; + tsConfigFiles?: string[]; +} + +// Users with custom webpack setup will need to let us know how to find their main and tsconfig files. +// If they don't, we look for the usual locations. +const defaultMainFiles = [ + 'index.ts', + 'index.js', + 'src/index.ts', + 'src/index.js', + 'src/main.ts', + 'src/main.tsx', + 'src/main.js', +]; +const defaultTsConfigFiles = [ + 'tsconfig.app.json', + 'tsconfig.lib.json', + 'tsconfig.json', +]; + +export const createNodes: CreateNodesAsync = [ + '**/webpack.config.{js,ts,mjs,mts,cjs,cts}', + async (configFilePath, options, context) => { + options.buildTargetName ??= 'build'; + options.serveTargetName ??= 'serve'; + 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 projectName = basename(projectRoot); + + return { + projects: { + [projectName]: { + root: projectRoot, + projectType: 'application', + targets: await createWebpackTargets( + configFilePath, + projectRoot, + options, + context + ), + }, + }, + }; + }, +]; + +async function createWebpackTargets( + configFilePath: string, + projectRoot: string, + options: WebpackPluginOptions, + context: CreateNodesContext +): Promise< + Record< + string, + TargetConfiguration + > +> { + const namedInputs = getNamedInputs(projectRoot, context); + const rootTsConfig = getRootTsConfigPath(); + + let webpackConfig = resolveUserDefinedWebpackConfig( + join(context.workspaceRoot, configFilePath), + rootTsConfig + ); + let target: 'node' | 'web' | 'webworker'; + let outputPath: string; + if (isNxWebpackComposablePlugin(webpackConfig)) { + webpackConfig = await webpackConfig( + {}, + { + options: {} as any, + context: {} as any, + } + ); + } else { + if (typeof webpackConfig === 'function') { + webpackConfig = webpackConfig( + { + production: true, // we want the production build options + }, + {} + ); + } + } + + outputPath = + webpackConfig.output?.path ?? '{workspaceRoot}/dist/{projectRoot}'; + target = webpackConfig?.target ?? 'web'; + + const { main, tsConfig } = findMainAndTsConfigFiles( + projectRoot, + options, + context + ); + + const baseBuildTargetConfig: TargetConfiguration = { + executor: '@nx/webpack:webpack', + options: { + target, + main, + tsConfig, + outputPath, + webpackConfig: configFilePath, + }, + }; + + const baseServeTargetConfig: TargetConfiguration = { + executor: '@nx/webpack:dev-server', + options: { + buildTarget: options.buildTargetName, + }, + }; + + const targets = {}; + + const buildTargetDefaults = readTargetDefaultsForTarget( + options.buildTargetName, + context.nxJsonConfiguration.targetDefaults, + '@nx/webpack:webpack' + ); + + targets[options.buildTargetName] = { + ...baseBuildTargetConfig, + options: { + ...baseBuildTargetConfig.options, + }, + }; + + if (buildTargetDefaults?.cache === undefined) { + targets[options.buildTargetName].cache = true; + } + + if (buildTargetDefaults?.inputs === undefined) { + targets[options.buildTargetName].inputs = + 'production' in namedInputs + ? ['default', '^production'] + : ['default', '^default']; + } + + if (buildTargetDefaults?.outputs === undefined) { + targets[options.buildTargetName].outputs = [outputPath]; + } + + const serveTargetDefaults = readTargetDefaultsForTarget( + options.serveTargetName, + context.nxJsonConfiguration.targetDefaults, + '@nx/webpack:dev-server' + ); + + targets[options.serveTargetName] = { + ...baseServeTargetConfig, + options: { + ...baseServeTargetConfig.options, + }, + }; + + if (serveTargetDefaults?.cache === undefined) { + targets[options.serveTargetName].cache = true; + } + + if (serveTargetDefaults?.inputs === undefined) { + targets[options.serveTargetName].inputs = + 'production' in namedInputs + ? ['default', '^production'] + : ['default', '^default']; + } + + if (serveTargetDefaults?.outputs === undefined) { + targets[options.serveTargetName].outputs = []; + } + + return targets; +} + +function findMainAndTsConfigFiles( + projectRoot: string, + options: WebpackPluginOptions, + context: CreateNodesContext +) { + const dir = join(context.workspaceRoot, projectRoot); + + let main = + applyBaseConfigOptionsFromLastInvocation?.main ?? + NxWebpackPlugin.optionsFromLastInvocation?.main; + let tsConfig = + applyBaseConfigOptionsFromLastInvocation?.tsConfig ?? + NxWebpackPlugin.optionsFromLastInvocation?.tsConfig; + + if (!main) { + const mainFiles = options.mainFiles ?? defaultMainFiles; + for (const file of mainFiles) { + if (existsSync(join(dir, file))) { + main = joinPathFragments(projectRoot, file); + break; + } + } + } + + if (!tsConfig) { + const tsConfigFiles = options.tsConfigFiles ?? defaultTsConfigFiles; + for (const file of tsConfigFiles) { + if (existsSync(join(dir, file))) { + tsConfig = joinPathFragments(projectRoot, file); + break; + } + } + } + + return { main, tsConfig }; +} diff --git a/packages/webpack/src/utils/config.spec.ts b/packages/webpack/src/utils/config.spec.ts index 7eaf6f7959060d..9c16ef8457f93e 100644 --- a/packages/webpack/src/utils/config.spec.ts +++ b/packages/webpack/src/utils/config.spec.ts @@ -2,6 +2,7 @@ import { composePluginsSync, composePlugins, NxWebpackExecutionContext, + isNxWebpackComposablePlugin, } from './config'; describe('composePlugins', () => { @@ -29,6 +30,7 @@ describe('composePlugins', () => { }; const combined = composePlugins(a(), b(), c(), d()); + expect(isNxWebpackComposablePlugin(combined)).toBeTruthy(); const config = await combined( { plugins: [] }, {} as NxWebpackExecutionContext @@ -59,6 +61,7 @@ describe('composePluginsSync', () => { }; const combined = composePluginsSync(a(), b()); + expect(isNxWebpackComposablePlugin(combined)).toBeTruthy(); const config = await combined( { plugins: [] }, {} as NxWebpackExecutionContext diff --git a/packages/webpack/src/utils/config.ts b/packages/webpack/src/utils/config.ts index ffadd834b67bb6..f7e53f428b70a6 100644 --- a/packages/webpack/src/utils/config.ts +++ b/packages/webpack/src/utils/config.ts @@ -2,28 +2,26 @@ import { ExecutorContext } from '@nx/devkit'; import { Configuration } from 'webpack'; import { NormalizedWebpackExecutorOptions } from '../executors/webpack/schema'; -import { withNx } from './with-nx'; -import { withWeb } from './with-web'; - -/** @deprecated use withNx and withWeb plugins directly */ -export function getBaseWebpackPartial( - options: NormalizedWebpackExecutorOptions, - context?: ExecutorContext -): Configuration { - const config: Configuration = {}; - const configure = composePluginsSync(withNx(), withWeb()); - return configure(config, { options, context }); + +export const nxWebpackComposablePlugin = Symbol('nxWebpackComposablePlugin'); + +export function isNxWebpackComposablePlugin( + a: unknown +): a is NxComposableWebpackPlugin { + return a?.[nxWebpackComposablePlugin] === true; } export interface NxWebpackExecutionContext { options: NormalizedWebpackExecutorOptions; context: ExecutorContext; + configuration?: string; } -export interface NxWebpackPlugin { +export interface NxComposableWebpackPlugin { (config: Configuration, ctx: NxWebpackExecutionContext): Configuration; } -export interface AsyncNxWebpackPlugin { + +export interface AsyncNxComposableWebpackPlugin { (config: Configuration, ctx: NxWebpackExecutionContext): | Configuration | Promise; @@ -31,31 +29,41 @@ export interface AsyncNxWebpackPlugin { export function composePlugins( ...plugins: ( - | NxWebpackPlugin - | AsyncNxWebpackPlugin - | Promise + | NxComposableWebpackPlugin + | AsyncNxComposableWebpackPlugin + | Promise )[] ) { - return async function combined( - config: Configuration, - ctx: NxWebpackExecutionContext - ): Promise { - for (const plugin of plugins) { - const fn = await plugin; - config = await fn(config, ctx); + return Object.assign( + async function combined( + config: Configuration, + ctx: NxWebpackExecutionContext + ): Promise { + for (const plugin of plugins) { + const fn = await plugin; + config = await fn(config, ctx); + } + return config; + }, + { + [nxWebpackComposablePlugin]: true, } - return config; - }; + ); } -export function composePluginsSync(...plugins: NxWebpackPlugin[]) { - return function combined( - config: Configuration, - ctx: NxWebpackExecutionContext - ): Configuration { - for (const plugin of plugins) { - config = plugin(config, ctx); +export function composePluginsSync(...plugins: NxComposableWebpackPlugin[]) { + return Object.assign( + function combined( + config: Configuration, + ctx: NxWebpackExecutionContext + ): Configuration { + for (const plugin of plugins) { + config = plugin(config, ctx); + } + return config; + }, + { + [nxWebpackComposablePlugin]: true, } - return config; - }; + ); } diff --git a/packages/webpack/src/utils/webpack/custom-webpack.ts b/packages/webpack/src/utils/webpack/resolve-user-defined-webpack-config.ts similarity index 83% rename from packages/webpack/src/utils/webpack/custom-webpack.ts rename to packages/webpack/src/utils/webpack/resolve-user-defined-webpack-config.ts index 82ed5a55506c5a..f93a5d6839d09e 100644 --- a/packages/webpack/src/utils/webpack/custom-webpack.ts +++ b/packages/webpack/src/utils/webpack/resolve-user-defined-webpack-config.ts @@ -1,6 +1,9 @@ import { registerTsProject } from '@nx/js/src/internal'; -export function resolveCustomWebpackConfig(path: string, tsConfig: string) { +export function resolveUserDefinedWebpackConfig( + path: string, + tsConfig: string +) { // Don't transpile non-TS files. This prevents workspaces libs from being registered via tsconfig-paths. // There's an issue here with Nx workspace where loading plugins from source (via tsconfig-paths) can lead to errors. if (!/\.(ts|mts|cts)$/.test(path)) { @@ -27,10 +30,3 @@ export function resolveCustomWebpackConfig(path: string, tsConfig: string) { return customWebpackConfig; } - -export function isRegistered() { - return ( - require.extensions['.ts'] != undefined || - require.extensions['.tsx'] != undefined - ); -} diff --git a/packages/webpack/src/utils/with-nx.ts b/packages/webpack/src/utils/with-nx.ts index 7daf5377da33dc..f80bec896d89d2 100644 --- a/packages/webpack/src/utils/with-nx.ts +++ b/packages/webpack/src/utils/with-nx.ts @@ -1,38 +1,51 @@ import { Configuration } from 'webpack'; -import { NxWebpackExecutionContext, NxWebpackPlugin } from './config'; +import { + NxWebpackExecutionContext, + NxComposableWebpackPlugin, + nxWebpackComposablePlugin, +} from './config'; import { applyBaseConfig } from '../plugins/nx-webpack-plugin/lib/apply-base-config'; const processed = new Set(); export interface WithNxOptions { + target?: 'node' | 'web' | 'webworker'; + main?: string; + tsConfig?: string; + outputPath?: string; skipTypeChecking?: boolean; } /** * @param {WithNxOptions} pluginOptions - * @returns {NxWebpackPlugin} + * @returns {NxComposableWebpackPlugin} */ -export function withNx(pluginOptions?: WithNxOptions): NxWebpackPlugin { - return function configure( - config: Configuration, - { options, context }: NxWebpackExecutionContext - ): Configuration { - if (processed.has(config)) return config; +export const withNx = Object.assign( + (pluginOptions?: WithNxOptions): NxComposableWebpackPlugin => { + return function configure( + config: Configuration, + { options, context }: NxWebpackExecutionContext + ): Configuration { + if (processed.has(config)) return config; - applyBaseConfig( - { - ...options, - ...pluginOptions, - root: context.root, - projectName: context.projectName, - targetName: context.targetName, - configurationName: context.configurationName, - projectGraph: context.projectGraph, - }, - config - ); + applyBaseConfig( + { + ...pluginOptions, + ...options, + root: context.root, + projectName: context.projectName, + targetName: context.targetName, + configurationName: context.configurationName, + projectGraph: context.projectGraph, + }, + config + ); - processed.add(config); - return config; - }; -} + processed.add(config); + return config; + }; + }, + { + [nxWebpackComposablePlugin]: true, + } +); diff --git a/packages/webpack/src/utils/with-web.ts b/packages/webpack/src/utils/with-web.ts index a3d3f30c00f081..089179fc8ca079 100644 --- a/packages/webpack/src/utils/with-web.ts +++ b/packages/webpack/src/utils/with-web.ts @@ -1,10 +1,11 @@ import { Configuration } from 'webpack'; -import { NxWebpackExecutionContext, NxWebpackPlugin } from './config'; import { - ExtraEntryPointClass, - NormalizedWebpackExecutorOptions, -} from '../executors/webpack/schema'; + nxWebpackComposablePlugin, + NxWebpackExecutionContext, + NxComposableWebpackPlugin, +} from './config'; +import { ExtraEntryPointClass } from '../executors/webpack/schema'; import { applyWebConfig } from '../plugins/nx-webpack-plugin/lib/apply-web-config'; const processed = new Set(); @@ -24,37 +25,35 @@ export interface WithWebOptions { ssr?: boolean; } -// Omit deprecated options -export type MergedOptions = Omit< - NormalizedWebpackExecutorOptions, - keyof WithWebOptions -> & - WithWebOptions; - /** * @param {WithWebOptions} pluginOptions - * @returns {NxWebpackPlugin} + * @returns {NxComposableWebpackPlugin} */ -export function withWeb(pluginOptions: WithWebOptions = {}): NxWebpackPlugin { - return function configure( - config: Configuration, - { options, context }: NxWebpackExecutionContext - ): Configuration { - if (processed.has(config)) return config; +export const withWeb = Object.assign( + (pluginOptions: WithWebOptions = {}): NxComposableWebpackPlugin => { + return function configure( + config: Configuration, + { options, context }: NxWebpackExecutionContext + ): Configuration { + if (processed.has(config)) return config; - applyWebConfig( - { - ...options, - ...pluginOptions, - projectName: context.projectName, - targetName: context.targetName, - configurationName: context.configurationName, - projectGraph: context.projectGraph, - }, - config - ); + applyWebConfig( + { + ...pluginOptions, + ...options, + projectName: context.projectName, + targetName: context.targetName, + configurationName: context.configurationName, + projectGraph: context.projectGraph, + }, + config + ); - processed.add(config); - return config; - }; -} + processed.add(config); + return config; + }; + }, + { + [nxWebpackComposablePlugin]: true, + } +);