diff --git a/docs/generated/packages/webpack/executors/webpack.json b/docs/generated/packages/webpack/executors/webpack.json index 7b5b12add6c9f5..9f5587e366fdee 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,18 +340,16 @@ }, "additionalProperties": false, "required": ["replace", "with"] - }, - "default": [] + } }, "buildLibsFromSource": { "type": "boolean", - "description": "Read buildable libraries from source instead of building them separately.", + "description": "Read buildable libraries from source instead of building them separately. If set to `false`, the `tsConfig` option must also be set to remap paths.", "default": true }, "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", @@ -400,7 +372,7 @@ "x-completion-type": "file" } }, - "required": ["tsConfig", "main"], + "required": [], "definitions": { "assetPattern": { "oneOf": [ diff --git a/packages/next/plugins/component-testing.ts b/packages/next/plugins/component-testing.ts index c921eda5614c47..c940fb1af6c3e1 100644 --- a/packages/next/plugins/component-testing.ts +++ b/packages/next/plugins/component-testing.ts @@ -112,12 +112,13 @@ Able to find CT project, ${!!ctProjectConfig}.`); ), }; const configure = composePluginsSync( - withNx(), - withReact({ + withNx({ + target: 'web', styles: [], scripts: [], postcssConfig: ctProjectConfig.root, - }) + }), + withReact({}) ); const webpackConfig = configure( {}, diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index f688abcc6668fe..9f35689cf2ea2a 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -37,14 +37,8 @@ describe('app', () => { outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { - target: 'node', - compiler: 'tsc', outputPath: 'dist/my-node-app', - main: 'my-node-app/src/main.ts', - tsConfig: 'my-node-app/tsconfig.app.json', - isolatedConfig: true, webpackConfig: 'my-node-app/webpack.config.js', - assets: ['my-node-app/src/assets'], }, configurations: { development: {}, diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index 939ce5b14429c2..f1cf6bf601b192 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -47,28 +47,16 @@ export interface NormalizedSchema extends Schema { parsedTags: string[]; } -function getWebpackBuildConfig( - project: ProjectConfiguration, - options: NormalizedSchema -): TargetConfiguration { +function getWebpackBuildConfig(options: NormalizedSchema): TargetConfiguration { return { executor: `@nx/webpack:webpack`, outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { - target: 'node', - compiler: 'tsc', outputPath: joinPathFragments( 'dist', options.rootProject ? options.name : options.appProjectRoot ), - main: joinPathFragments( - project.sourceRoot, - 'main' + (options.js ? '.js' : '.ts') - ), - tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), - assets: [joinPathFragments(project.sourceRoot, 'assets')], - isolatedConfig: true, webpackConfig: joinPathFragments( options.appProjectRoot, 'webpack.config.js' @@ -157,7 +145,7 @@ function addProject(tree: Tree, options: NormalizedSchema) { project.targets.build = options.bundler === 'esbuild' ? getEsBuildConfig(project, options) - : getWebpackBuildConfig(project, options); + : getWebpackBuildConfig(options); project.targets.serve = getServeConfig(options); addProjectConfiguration( @@ -169,6 +157,7 @@ function addProject(tree: Tree, options: NormalizedSchema) { } function addAppFiles(tree: Tree, options: NormalizedSchema) { + const sourceRoot = joinPathFragments(options.appProjectRoot, 'src'); generateFiles( tree, join(__dirname, './files/common'), @@ -183,6 +172,17 @@ function addAppFiles(tree: Tree, options: NormalizedSchema) { tree, options.appProjectRoot ), + webpackPluginOptions: { + main: joinPathFragments( + sourceRoot, + 'main' + (options.js ? '.js' : '.ts') + ), + tsConfig: joinPathFragments( + options.appProjectRoot, + 'tsconfig.app.json' + ), + assets: [joinPathFragments(sourceRoot, 'assets')], + }, } ); diff --git a/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ b/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ index e51dd73f9ca0ae..6b1ebba4faac45 100644 --- a/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ +++ b/packages/node/src/generators/application/files/common/webpack.config.js__tmpl__ @@ -1,8 +1,17 @@ const { composePlugins, withNx} = require('@nx/webpack'); // Nx plugins for webpack. -module.exports = composePlugins(withNx(), (config) => { - // Update the webpack config as needed here. - // e.g. `config.plugins.push(new MyPlugin())` - return config; -}); +module.exports = composePlugins( + withNx({ + target: 'node', + compiler: 'tsc', + main: '<%= webpackPluginOptions.main %>', + tsConfig: '<%= webpackPluginOptions.tsConfig %>', + assets: <%- JSON.stringify(webpackPluginOptions.assets) %>, + }), + (config) => { + // Update the webpack config as needed here. + // e.g. `config.plugins.push(new MyPlugin())` + return config; + } +); diff --git a/packages/react/plugins/component-testing/index.ts b/packages/react/plugins/component-testing/index.ts index 7cde7d951cb505..8d1e96a99f60aa 100644 --- a/packages/react/plugins/component-testing/index.ts +++ b/packages/react/plugins/component-testing/index.ts @@ -8,7 +8,6 @@ import { joinPathFragments, logger, parseTargetString, - ProjectGraph, readCachedProjectGraph, readTargetOptions, stripIndents, @@ -235,11 +234,9 @@ function buildTargetWebpack( normalizeOptions, } = require('@nx/webpack/src/executors/webpack/lib/normalize-options'); const { - resolveCustomWebpackConfig, - } = require('@nx/webpack/src/utils/webpack/custom-webpack'); - const { - getWebpackConfig, - } = require('@nx/webpack/src/executors/webpack/lib/get-webpack-config'); + resolveUserDefinedWebpackConfig, + } = require('@nx/webpack/src/utils/webpack/resolve-user-defined-webpack-config'); + const { withNx } = require('@nx/webpack/src/utils/with-nx'); const options = normalizeOptions( withSchemaDefaults(parsed, context), @@ -251,7 +248,7 @@ function buildTargetWebpack( let customWebpack: any; if (options.webpackConfig) { - customWebpack = resolveCustomWebpackConfig( + customWebpack = resolveUserDefinedWebpackConfig( options.webpackConfig, options.tsConfig.startsWith(context.root) ? options.tsConfig @@ -262,12 +259,8 @@ function buildTargetWebpack( return async () => { customWebpack = await customWebpack; // TODO(jack): Once webpackConfig is always set in @nx/webpack:webpack, we no longer need this default. - const defaultWebpack = getWebpackConfig(context, { + const defaultWebpack = withNx({ ...options, - // cypress will generate its own index.html from component-index.html - generateIndexHtml: false, - // causes issues with buildable libraries with ENOENT: no such file or directory, scandir error - extractLicenses: false, root: workspaceRoot, projectRoot: ctProjectConfig.root, sourceRoot: ctProjectConfig.sourceRoot, 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/plugins/storybook/index.ts b/packages/react/plugins/storybook/index.ts index afa2c65a8cf1bf..1835e812b3e4c8 100644 --- a/packages/react/plugins/storybook/index.ts +++ b/packages/react/plugins/storybook/index.ts @@ -196,8 +196,7 @@ export const webpack = async ( // ESM build for modern browsers. let baseWebpackConfig: Configuration = {}; const configure = composePluginsSync( - withNx({ skipTypeChecking: true }), - withWeb(), + withNx({ target: 'web', skipTypeChecking: true }), withReact() ); const finalConfig = configure(baseWebpackConfig, { diff --git a/packages/react/plugins/with-react.ts b/packages/react/plugins/with-react.ts index a4ceac7e9bcf32..444cdf009eb30a 100644 --- a/packages/react/plugins/with-react.ts +++ b/packages/react/plugins/with-react.ts @@ -1,10 +1,10 @@ import type { Configuration } from 'webpack'; -import type { NxWebpackExecutionContext, WithWebOptions } from '@nx/webpack'; +import type { NxWebpackExecutionContext } from '@nx/webpack'; import { applyReactConfig } from './nx-react-webpack-plugin/lib/apply-react-config'; const processed = new Set(); -interface WithReactOptions extends WithWebOptions { +export interface WithReactOptions { svgr?: false; } @@ -17,15 +17,8 @@ export function withReact(pluginOptions: WithReactOptions = {}) { config: Configuration, context: NxWebpackExecutionContext ): Configuration { - const { withWeb } = require('@nx/webpack'); - if (processed.has(config)) return config; - - // Apply web config for CSS, JSX, index.html handling, etc. - config = withWeb(pluginOptions)(config, context); - applyReactConfig(pluginOptions, config); - processed.add(config); return config; }; diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 4246cb9ade4a59..a325b46d8ce273 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -42,7 +42,41 @@ describe('app', () => { expect(projects.get('my-app').root).toEqual('my-app'); expect(projects.get('my-app-e2e').root).toEqual('my-app-e2e'); - }); + + expect(appTree.read('my-app/webpack.config.js', 'utf-8')) + .toMatchInlineSnapshot(` + "const { composePlugins, withNx } = require('@nx/webpack'); + const { withReact } = require('@nx/react'); + + // Nx plugins for webpack. + module.exports = composePlugins( + withNx({ + target: 'web', + compiler: 'babel', + outputPath: 'dist/my-app', + index: 'my-app/src/index.html', + main: 'my-app/src/main.tsx', + tsConfig: 'my-app/tsconfig.app.json', + baseHref: '/', + assets: ['my-app/src/favicon.ico', 'my-app/src/assets'], + styles: ['my-app/src/styles.css'], + outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none', + optimization: process.env['NODE_ENV'] === 'production', + }), + withReact({ + // Uncomment this line if you don't want to use SVGR + // See: https://react-svgr.com/ + // svgr: false + }), + (config) => { + // Update the webpack config as needed here. + // e.g. \`config.plugins.push(new MyPlugin())\` + return config; + } + ); + " + `); + }, 60_000); it('should add vite types to tsconfigs', async () => { await applicationGenerator(appTree, { @@ -379,32 +413,23 @@ describe('app', () => { expect(targetConfig.build.executor).toEqual('@nx/webpack:webpack'); expect(targetConfig.build.outputs).toEqual(['{options.outputPath}']); expect(targetConfig.build.options).toEqual({ - compiler: 'babel', - assets: ['my-app/src/favicon.ico', 'my-app/src/assets'], - index: 'my-app/src/index.html', - main: 'my-app/src/main.tsx', - baseHref: '/', outputPath: 'dist/my-app', - scripts: [], - styles: ['my-app/src/styles.css'], - tsConfig: 'my-app/tsconfig.app.json', - isolatedConfig: true, webpackConfig: 'my-app/webpack.config.js', }); expect(targetConfig.build.configurations.production).toEqual({ optimization: true, - extractLicenses: true, - fileReplacements: [ - { - replace: 'my-app/src/environments/environment.ts', - with: 'my-app/src/environments/environment.prod.ts', - }, - ], - namedChunks: false, outputHashing: 'all', - sourceMap: false, - vendorChunk: false, }); + const webpackConfig = appTree.read('my-app/webpack.config.js', 'utf-8'); + expect(webpackConfig).toContain(`compiler: 'babel'`); + expect(webpackConfig).toContain( + `assets: ['my-app/src/favicon.ico', 'my-app/src/assets']` + ); + expect(webpackConfig).toContain(`index: 'my-app/src/index.html'`); + expect(webpackConfig).toContain(`main: 'my-app/src/main.tsx'`); + expect(webpackConfig).toContain(`baseHref: '/'`); + expect(webpackConfig).toContain(`styles: ['my-app/src/styles.css']`); + expect(webpackConfig).toContain(`tsConfig: 'my-app/tsconfig.app.json'`); }); it('should setup the nx vite builder if bundler is vite', async () => { @@ -689,11 +714,9 @@ describe('app', () => { bundler: 'webpack', }); - const projectsConfigurations = getProjects(appTree); - - expect( - projectsConfigurations.get('my-app').targets.build.options.styles - ).toEqual([]); + expect(appTree.read('my-app/webpack.config.js', 'utf-8')).toContain( + `styles: []` + ); }); it('should not break if bundler is vite', async () => { @@ -774,25 +797,9 @@ describe('app', () => { bundler: 'webpack', }); - const projectsConfigurations = getProjects(appTree); - - expect( - projectsConfigurations.get('my-app').targets.build.options.styles - ).toEqual([]); - }); - - it('should not break if bundler is vite', async () => { - await applicationGenerator(appTree, { - ...schema, - style: '@emotion/styled', - bundler: 'vite', - }); - - const projectsConfigurations = getProjects(appTree); - - expect( - projectsConfigurations.get('my-app').targets.build.options.styles - ).toBeUndefined(); + expect(appTree.read('my-app/webpack.config.js', 'utf-8')).toContain( + `styles: []` + ); }); it('should add dependencies to package.json', async () => { diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index f6377619755b2c..ef117b5483a799 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -103,9 +103,19 @@ export async function applicationGeneratorInternal( skipFormat: true, skipHelperLibs: options.bundler === 'vite', }); - tasks.push(initTask); + if (options.bundler === 'webpack') { + const { webpackInitGenerator } = ensurePackage< + typeof import('@nx/webpack') + >('@nx/webpack', nxVersion); + const webpackInitTask = await webpackInitGenerator(host, { + uiFramework: 'react', + skipFormat: true, + }); + tasks.push(webpackInitTask); + } + if (!options.rootProject) { extractTsConfigBase(host); } @@ -157,15 +167,6 @@ export async function applicationGeneratorInternal( }, false ); - } else if (options.bundler === 'webpack') { - const { webpackInitGenerator } = ensurePackage< - typeof import('@nx/webpack') - >('@nx/webpack', nxVersion); - const webpackInitTask = await webpackInitGenerator(host, { - uiFramework: 'react', - skipFormat: true, - }); - tasks.push(webpackInitTask); } else if (options.bundler === 'rspack') { const { configurationGenerator } = ensurePackage( '@nx/rspack', diff --git a/packages/react/src/generators/application/files/base-webpack/src/environments/environment.prod.ts__tmpl__ b/packages/react/src/generators/application/files/base-webpack/src/environments/environment.prod.ts__tmpl__ deleted file mode 100644 index 3612073bc31cd4..00000000000000 --- a/packages/react/src/generators/application/files/base-webpack/src/environments/environment.prod.ts__tmpl__ +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/packages/react/src/generators/application/files/base-webpack/src/environments/environment.ts__tmpl__ b/packages/react/src/generators/application/files/base-webpack/src/environments/environment.ts__tmpl__ deleted file mode 100644 index d9370e924b51bc..00000000000000 --- a/packages/react/src/generators/application/files/base-webpack/src/environments/environment.ts__tmpl__ +++ /dev/null @@ -1,6 +0,0 @@ -// This file can be replaced during build by using the `fileReplacements` array. -// When building for production, this file is replaced with `environment.prod.ts`. - -export const environment = { - production: false -}; diff --git a/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ b/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ index f92d94803544c7..ba3394c5f3571a 100644 --- a/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ +++ b/packages/react/src/generators/application/files/base-webpack/webpack.config.js__tmpl__ @@ -2,8 +2,28 @@ const { composePlugins, withNx } = require('@nx/webpack'); const { withReact } = require('@nx/react'); // Nx plugins for webpack. -module.exports = composePlugins(withNx(), withReact(), (config) => { - // Update the webpack config as needed here. - // e.g. `config.plugins.push(new MyPlugin())` - return config; -}); +module.exports = composePlugins( + withNx({ + target: '<%= pluginOptions.target %>', + compiler: '<%= pluginOptions.compiler %>', + outputPath: '<%= pluginOptions.outputPath %>', + index: '<%= pluginOptions.index %>', + main: '<%= pluginOptions.main %>', + tsConfig: '<%= pluginOptions.tsConfig %>', + baseHref: '<%= pluginOptions.baseHref %>', + assets: <%- JSON.stringify(pluginOptions.assets) %>, + styles: <%- JSON.stringify(pluginOptions.styles) %>, + outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none', + optimization: process.env['NODE_ENV'] === 'production', + }), + withReact({ + // Uncomment this line if you don't want to use SVGR + // See: https://react-svgr.com/ + // svgr: false + }), + (config) => { + // Update the webpack config as needed here. + // e.g. `config.plugins.push(new MyPlugin())` + return config; + } +); diff --git a/packages/react/src/generators/application/lib/add-e2e.ts b/packages/react/src/generators/application/lib/add-e2e.ts index 42753920f9a86a..3a894759ee6c0e 100644 --- a/packages/react/src/generators/application/lib/add-e2e.ts +++ b/packages/react/src/generators/application/lib/add-e2e.ts @@ -4,10 +4,12 @@ import { ensurePackage, getPackageManagerCommand, joinPathFragments, + readNxJson, } from '@nx/devkit'; import { webStaticServeGenerator } from '@nx/web'; import { nxVersion } from '../../../utils/versions'; +import { hasPlugin } from '@nx/webpack/src/utils/has-plugin'; import { NormalizedSchema } from '../schema'; export async function addE2e( @@ -16,10 +18,12 @@ export async function addE2e( ): Promise { switch (options.e2eTestRunner) { case 'cypress': { - webStaticServeGenerator(tree, { - buildTarget: `${options.projectName}:build`, - targetName: 'serve-static', - }); + if (!hasPlugin(tree)) { + webStaticServeGenerator(tree, { + buildTarget: `${options.projectName}:build`, + targetName: 'serve-static', + }); + } const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index 5a2075995e7793..dc9fd495cc3368 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -4,9 +4,11 @@ import { joinPathFragments, ProjectConfiguration, TargetConfiguration, + Tree, } from '@nx/devkit'; +import { hasPlugin } from '@nx/webpack/src/utils/has-plugin'; -export function addProject(host, options: NormalizedSchema) { +export function addProject(host: Tree, options: NormalizedSchema) { const project: ProjectConfiguration = { root: options.appProjectRoot, sourceRoot: `${options.appProjectRoot}/src`, @@ -15,7 +17,7 @@ export function addProject(host, options: NormalizedSchema) { tags: options.parsedTags, }; - if (options.bundler === 'webpack') { + if (options.bundler === 'webpack' && !hasPlugin(host)) { project.targets = { build: createBuildTarget(options), serve: createServeTarget(options), @@ -39,35 +41,12 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration { outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { - compiler: options.compiler ?? 'babel', outputPath: joinPathFragments( 'dist', options.appProjectRoot != '.' ? options.appProjectRoot : options.projectName ), - index: joinPathFragments(options.appProjectRoot, 'src/index.html'), - baseHref: '/', - main: joinPathFragments( - options.appProjectRoot, - maybeJs(options, `src/main.tsx`) - ), - tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), - assets: [ - joinPathFragments(options.appProjectRoot, 'src/favicon.ico'), - joinPathFragments(options.appProjectRoot, 'src/assets'), - ], - styles: - options.styledModule || !options.hasStyles - ? [] - : [ - joinPathFragments( - options.appProjectRoot, - `src/styles.${options.style}` - ), - ], - scripts: [], - isolatedConfig: true, webpackConfig: joinPathFragments( options.appProjectRoot, 'webpack.config.js' @@ -75,30 +54,11 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration { }, configurations: { development: { - extractLicenses: false, optimization: false, - sourceMap: true, - vendorChunk: true, }, production: { - fileReplacements: [ - { - replace: joinPathFragments( - options.appProjectRoot, - maybeJs(options, `src/environments/environment.ts`) - ), - with: joinPathFragments( - options.appProjectRoot, - maybeJs(options, `src/environments/environment.prod.ts`) - ), - }, - ], optimization: true, outputHashing: 'all', - sourceMap: false, - namedChunks: false, - extractLicenses: true, - vendorChunk: false, }, }, }; diff --git a/packages/react/src/generators/application/lib/create-application-files.ts b/packages/react/src/generators/application/lib/create-application-files.ts index 1190f06a26ae1e..82234780984006 100644 --- a/packages/react/src/generators/application/lib/create-application-files.ts +++ b/packages/react/src/generators/application/lib/create-application-files.ts @@ -1,5 +1,6 @@ import { generateFiles, + joinPathFragments, names, offsetFromRoot, toJS, @@ -12,6 +13,9 @@ import { createTsConfig } from '../../../utils/create-ts-config'; import { getInSourceVitestTestsTemplate } from '../../../utils/get-in-source-vitest-tests-template'; import { NormalizedSchema } from '../schema'; import { getAppTests } from './get-app-tests'; +import { maybeJs } from './add-project'; +import { WithReactOptions } from '../../../../plugins/with-react'; +import { WithNxOptions } from '@nx/webpack'; export function createApplicationFiles(host: Tree, options: NormalizedSchema) { let styleSolutionSpecificAppFiles: string; @@ -53,7 +57,10 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { host, join(__dirname, '../files/base-webpack'), options.appProjectRoot, - templateVariables + { + ...templateVariables, + pluginOptions: createComposableWebpackPluginOptions(options), + } ); if (options.compiler === 'babel') { writeJson(host, `${options.appProjectRoot}/.babelrc`, { @@ -154,3 +161,38 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { relativePathToRootTsConfig ); } + +function createComposableWebpackPluginOptions( + options: NormalizedSchema +): WithNxOptions & WithReactOptions { + return { + target: 'web', + compiler: options.compiler ?? 'babel', + outputPath: joinPathFragments( + 'dist', + options.appProjectRoot != '.' + ? options.appProjectRoot + : options.projectName + ), + index: joinPathFragments(options.appProjectRoot, 'src/index.html'), + baseHref: '/', + main: joinPathFragments( + options.appProjectRoot, + maybeJs(options, `src/main.tsx`) + ), + tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), + assets: [ + joinPathFragments(options.appProjectRoot, 'src/favicon.ico'), + joinPathFragments(options.appProjectRoot, 'src/assets'), + ], + styles: + options.styledModule || !options.hasStyles + ? [] + : [ + joinPathFragments( + options.appProjectRoot, + `src/styles.${options.style}` + ), + ], + }; +} diff --git a/packages/react/src/generators/library/lib/normalize-options.ts b/packages/react/src/generators/library/lib/normalize-options.ts index 2c19088ef9735c..deb91908d986e6 100644 --- a/packages/react/src/generators/library/lib/normalize-options.ts +++ b/packages/react/src/generators/library/lib/normalize-options.ts @@ -1,4 +1,10 @@ -import { getProjects, logger, normalizePath, Tree } from '@nx/devkit'; +import { + getProjects, + joinPathFragments, + logger, + normalizePath, + Tree, +} from '@nx/devkit'; import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { assertValidStyle } from '../../../utils/assertion'; import { NormalizedSchema, Schema } from '../schema'; @@ -74,10 +80,13 @@ export async function normalizeOptions( ); } - try { - normalized.appMain = appProjectConfig.targets.build.options.main; - normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot); - } catch (e) { + normalized.appMain = + appProjectConfig.targets.build.options.main ?? + findMainEntry(host, appProjectConfig.root); + normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot); + + // TODO(jack): We should use appEntryFile instead of appProject so users can directly set it rather than us inferring it. + if (!normalized.appMain) { throw new Error( `Could not locate project main for ${options.appProject}` ); @@ -88,3 +97,30 @@ export async function normalizeOptions( return normalized; } + +function findMainEntry(tree: Tree, projectRoot: string): string | undefined { + const mainFiles = [ + // These are the main files we generate with. + 'src/main.ts', + 'src/main.tsx', + 'src/main.js', + 'src/main.jsx', + // Other options just in case + 'src/index.ts', + 'src/index.tsx', + 'src/index.js', + 'src/index.jsx', + 'main.ts', + 'main.tsx', + 'main.js', + 'main.jsx', + 'index.ts', + 'index.tsx', + 'index.js', + 'index.jsx', + ]; + const mainEntry = mainFiles.find((file) => + tree.exists(joinPathFragments(projectRoot, file)) + ); + return mainEntry ? joinPathFragments(projectRoot, mainEntry) : undefined; +} 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/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 6d8a1165a1e91f..65591da40535c6 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -1,6 +1,10 @@ import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version'; -import { readProjectConfiguration, Tree } from '@nx/devkit'; -import { getProjects, readJson } from '@nx/devkit'; +import { + getProjects, + readJson, + readProjectConfiguration, + Tree, +} from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { applicationGenerator } from './application'; @@ -358,30 +362,12 @@ describe('app', () => { expect(targets.build.executor).toEqual('@nx/webpack:webpack'); expect(targets.build.outputs).toEqual(['{options.outputPath}']); expect(targets.build.options).toEqual({ - compiler: 'babel', - assets: ['my-app/src/favicon.ico', 'my-app/src/assets'], - index: 'my-app/src/index.html', - baseHref: '/', - main: 'my-app/src/main.ts', outputPath: 'dist/my-app', - scripts: [], - styles: ['my-app/src/styles.css'], - tsConfig: 'my-app/tsconfig.app.json', webpackConfig: 'my-app/webpack.config.js', }); expect(targets.build.configurations.production).toEqual({ optimization: true, - extractLicenses: true, - fileReplacements: [ - { - replace: 'my-app/src/environments/environment.ts', - with: 'my-app/src/environments/environment.prod.ts', - }, - ], - namedChunks: false, outputHashing: 'all', - sourceMap: false, - vendorChunk: false, }); }); diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 811ae54dc9c074..ea0f7769b8168e 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -37,26 +37,58 @@ interface NormalizedSchema extends Schema { } function createApplicationFiles(tree: Tree, options: NormalizedSchema) { - generateFiles( - tree, - join( - __dirname, - options.bundler === 'vite' ? './files/app-vite' : './files/app-webpack' - ), - options.appProjectRoot, - { - ...options, - ...names(options.name), - tmpl: '', - offsetFromRoot: offsetFromRoot(options.appProjectRoot), - rootTsConfigPath: getRelativePathToRootTsConfig( - tree, - options.appProjectRoot - ), + if (options.bundler === 'vite') { + generateFiles( + tree, + join(__dirname, './files/app-vite'), + options.appProjectRoot, + { + ...options, + ...names(options.name), + tmpl: '', + offsetFromRoot: offsetFromRoot(options.appProjectRoot), + rootTsConfigPath: getRelativePathToRootTsConfig( + tree, + options.appProjectRoot + ), + } + ); + } else { + generateFiles( + tree, + join(__dirname, './files/app-webpack'), + options.appProjectRoot, + { + ...options, + ...names(options.name), + tmpl: '', + offsetFromRoot: offsetFromRoot(options.appProjectRoot), + rootTsConfigPath: getRelativePathToRootTsConfig( + tree, + options.appProjectRoot + ), + pluginOptions: { + target: 'web', + assets: [ + joinPathFragments(options.appProjectRoot, 'src/favicon.ico'), + joinPathFragments(options.appProjectRoot, 'src/assets'), + ], + index: joinPathFragments(options.appProjectRoot, 'src/index.html'), + baseHref: '/', + styles: [ + joinPathFragments( + options.appProjectRoot, + `src/styles.${options.style}` + ), + ], + }, + } + ); + if (options.unitTestRunner === 'none') { + tree.delete( + join(options.appProjectRoot, './src/app/app.element.spec.ts') + ); } - ); - if (options.unitTestRunner === 'none') { - tree.delete(join(options.appProjectRoot, './src/app/app.element.spec.ts')); } } @@ -66,16 +98,12 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) { options.appProjectRoot, 'tsconfig.app.json' ); - const assets = [ - joinPathFragments(options.appProjectRoot, 'src/favicon.ico'), - joinPathFragments(options.appProjectRoot, 'src/assets'), - ]; - if (options.bundler === 'webpack') { const { configurationGenerator } = ensurePackage< typeof import('@nx/webpack') >('@nx/webpack', nxVersion); await configurationGenerator(tree, { + target: 'web', project: options.projectName, main, tsConfig, @@ -87,42 +115,6 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) { ), skipFormat: true, }); - const project = readProjectConfiguration(tree, options.projectName); - const prodConfig = project.targets.build.configurations.production; - const buildOptions = project.targets.build.options; - buildOptions.assets = assets; - buildOptions.index = joinPathFragments( - options.appProjectRoot, - 'src/index.html' - ); - buildOptions.baseHref = '/'; - buildOptions.styles = [ - joinPathFragments(options.appProjectRoot, `src/styles.${options.style}`), - ]; - // We can delete that, because this projest is an application - // and applications have a .babelrc file in their root dir. - // So Nx will find it and use it - delete buildOptions.babelUpwardRootMode; - buildOptions.scripts = []; - prodConfig.fileReplacements = [ - { - replace: joinPathFragments( - options.appProjectRoot, - `src/environments/environment.ts` - ), - with: joinPathFragments( - options.appProjectRoot, - `src/environments/environment.prod.ts` - ), - }, - ]; - prodConfig.optimization = true; - prodConfig.outputHashing = 'all'; - prodConfig.sourceMap = false; - prodConfig.namedChunks = false; - prodConfig.extractLicenses = true; - prodConfig.vendorChunk = false; - updateProjectConfiguration(tree, options.projectName, project); } else if (options.bundler === 'none') { // TODO(jack): Flush this out... no bundler should be possible for web but the experience isn't holistic due to missing features (e.g. writing index.html). const project = readProjectConfiguration(tree, options.projectName); @@ -133,7 +125,6 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) { main, outputPath: joinPathFragments('dist', options.appProjectRoot), tsConfig, - assets, }, }; updateProjectConfiguration(tree, options.projectName, project); diff --git a/packages/web/src/generators/application/files/app-webpack/src/environments/environment.prod.ts__tmpl__ b/packages/web/src/generators/application/files/app-webpack/src/environments/environment.prod.ts__tmpl__ deleted file mode 100644 index 3612073bc31cd4..00000000000000 --- a/packages/web/src/generators/application/files/app-webpack/src/environments/environment.prod.ts__tmpl__ +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/packages/web/src/generators/application/files/app-webpack/src/environments/environment.ts__tmpl__ b/packages/web/src/generators/application/files/app-webpack/src/environments/environment.ts__tmpl__ deleted file mode 100644 index d9370e924b51bc..00000000000000 --- a/packages/web/src/generators/application/files/app-webpack/src/environments/environment.ts__tmpl__ +++ /dev/null @@ -1,6 +0,0 @@ -// This file can be replaced during build by using the `fileReplacements` array. -// When building for production, this file is replaced with `environment.prod.ts`. - -export const environment = { - production: false -}; diff --git a/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ b/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ index 793c7b31eb0907..c6f3a042e00531 100644 --- a/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ +++ b/packages/web/src/generators/application/files/app-webpack/webpack.config.js__tmpl__ @@ -1,8 +1,19 @@ const { composePlugins, withNx, withWeb } = require('@nx/webpack'); // Nx plugins for webpack. -module.exports = composePlugins(withNx(), withWeb(), (config) => { - // Update the webpack config as needed here. - // e.g. `config.plugins.push(new MyPlugin())` - return config; -}); +module.exports = composePlugins( + withNx({ + target: '<%= pluginOptions.target %>', + index: '<%= pluginOptions.index %>', + baseHref: '<%= pluginOptions.baseHref %>', + assets: <%- JSON.stringify(pluginOptions.assets) %>, + styles: <%- JSON.stringify(pluginOptions.styles) %>, + outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none', + optimization: process.env['NODE_ENV'] === 'production', + }), + (config) => { + // Update the webpack config as needed here. + // e.g. `config.plugins.push(new MyPlugin())` + return config; + } +); 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..0dea24c63e6345 100644 --- a/packages/webpack/src/executors/dev-server/dev-server.impl.ts +++ b/packages/webpack/src/executors/dev-server/dev-server.impl.ts @@ -9,17 +9,18 @@ import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await'; import { map, tap } from 'rxjs/operators'; import * as WebpackDevServer from 'webpack-dev-server'; -import { getDevServerConfig } from './lib/get-dev-server-config'; +import { getDevServerOptions } from './lib/get-dev-server-config'; import { calculateProjectBuildableDependencies, 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'; +import { getRootTsConfigPath } from '@nx/js'; export async function* devServerExecutor( serveOptions: WebDevServerOptions, @@ -37,13 +38,13 @@ 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}.` - ); - } - + // TODO(jack): Figure out a way to port this into NxWebpackPlugin if (!buildOptions.buildLibsFromSource) { + if (!buildOptions.tsConfig) { + throw new Error( + `Cannot find "tsConfig" to remap paths for. Set this option in project.json.` + ); + } const { target, dependencies } = calculateProjectBuildableDependencies( context.taskGraph, context.projectGraph, @@ -60,37 +61,35 @@ export async function* devServerExecutor( ); } - let config = getDevServerConfig(context, buildOptions, serveOptions); + let config; + + const devServer = getDevServerOptions( + context.root, + serveOptions, + buildOptions + ); if (buildOptions.webpackConfig) { - let tsconfigPath = buildOptions.tsConfig.startsWith(context.root) - ? buildOptions.tsConfig - : join(context.root, buildOptions.tsConfig); - let customWebpack = resolveCustomWebpackConfig( + let userDefinedWebpackConfig = resolveUserDefinedWebpackConfig( buildOptions.webpackConfig, - tsconfigPath + getRootTsConfigPath() ); - if (typeof customWebpack.then === 'function') { - customWebpack = await customWebpack; + if (typeof userDefinedWebpackConfig.then === 'function') { + userDefinedWebpackConfig = await userDefinedWebpackConfig; } - if (typeof customWebpack === 'function') { - // Old behavior, call the webpack function that is specific to Nx - config = await customWebpack(config, { - options: buildOptions, - context, - configuration: serveOptions.buildTarget.split(':')[2], - }); - } else if (customWebpack) { - // New behavior, use the config object as is with devServer defaults - config = { - devServer: { - ...customWebpack.devServer, - ...config.devServer, - }, - ...customWebpack, - }; + // Only add the dev server option if user is composable plugin. + // Otherwise, user should define `devServer` option directly in their webpack config. + if (isNxWebpackComposablePlugin(userDefinedWebpackConfig)) { + config = await userDefinedWebpackConfig( + { devServer }, + { + options: buildOptions, + context, + configuration: serveOptions.buildTarget.split(':')[2], + } + ); } } 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..d28499e9071cf6 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 @@ -1,36 +1,14 @@ -import { ExecutorContext, logger } from '@nx/devkit'; -import type { Configuration as WebpackConfiguration } from 'webpack'; +import { logger } from '@nx/devkit'; import type { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'; import * as path from 'path'; import { readFileSync } from 'fs'; - -import { getWebpackConfig } from '../../webpack/lib/get-webpack-config'; import { WebDevServerOptions } from '../schema'; import { buildServePath } from './serve-path'; import { NormalizedWebpackExecutorOptions } from '../../webpack/schema'; -export function getDevServerConfig( - context: ExecutorContext, - buildOptions: NormalizedWebpackExecutorOptions, - serveOptions: WebDevServerOptions -): Partial { - const workspaceRoot = context.root; - const webpackConfig = buildOptions.isolatedConfig - ? {} - : getWebpackConfig(context, buildOptions); - - (webpackConfig as any).devServer = getDevServerPartial( - workspaceRoot, - serveOptions, - buildOptions - ); - - return webpackConfig as WebpackConfiguration; -} - -function getDevServerPartial( +export function getDevServerOptions( root: string, - options: WebDevServerOptions, + serveOptions: WebDevServerOptions, buildOptions: NormalizedWebpackExecutorOptions ): WebpackDevServerConfiguration { const servePath = buildServePath(buildOptions); @@ -47,11 +25,14 @@ function getDevServerPartial( } const config: WebpackDevServerConfiguration = { - host: options.host, - port: options.port, + // TODO(wip): These should be read from actual config + host: serveOptions.host ?? 'localhost', + port: serveOptions.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'], }, @@ -66,7 +47,7 @@ function getDevServerPartial( )}` ); }, - open: options.open, + open: serveOptions.open, static: false, compress: scriptsOptimization || stylesOptimization, devMiddleware: { @@ -74,31 +55,31 @@ function getDevServerPartial( stats: false, }, client: { - webSocketURL: options.publicHost, + webSocketURL: serveOptions.publicHost, overlay: { errors: !(scriptsOptimization || stylesOptimization), warnings: false, }, }, - liveReload: options.hmr ? false : options.liveReload, // disable liveReload if hmr is enabled - hot: options.hmr, + liveReload: serveOptions.hmr ? false : serveOptions.liveReload, // disable liveReload if hmr is enabled + hot: serveOptions.hmr, }; - if (options.ssl) { + if (serveOptions.ssl) { config.server = { type: 'https', }; - if (options.sslKey && options.sslCert) { - config.server.options = getSslConfig(root, options); + if (serveOptions.sslKey && serveOptions.sslCert) { + config.server.options = getSslConfig(root, serveOptions); } } - if (options.proxyConfig) { - config.proxy = getProxyConfig(root, options); + if (serveOptions.proxyConfig) { + config.proxy = getProxyConfig(root, serveOptions); } - if (options.allowedHosts) { - config.allowedHosts = options.allowedHosts.split(','); + if (serveOptions.allowedHosts) { + config.allowedHosts = serveOptions.allowedHosts.split(','); } return config; 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/get-webpack-config.ts b/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts deleted file mode 100644 index bf1e4cca41247b..00000000000000 --- a/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Configuration } from 'webpack'; -import { ExecutorContext } from '@nx/devkit'; - -import { NormalizedWebpackExecutorOptions } from '../schema'; -import { withNx } from '../../../utils/with-nx'; -import { withWeb } from '../../../utils/with-web'; -import { composePluginsSync } from '../../../utils/config'; - -interface GetWebpackConfigOverrides { - root: string; - sourceRoot: string; - configuration?: string; -} - -/** @deprecated Use withNx, withWeb, or withReact */ -// TODO(jack): Remove in Nx 16 -export function getWebpackConfig( - context: ExecutorContext, - options: NormalizedWebpackExecutorOptions -): Configuration { - const config: Configuration = {}; - const configure = - options.target === 'web' - ? composePluginsSync(withNx(), withWeb()) - : withNx(); - return configure(config, { options, context }); -} diff --git a/packages/webpack/src/executors/webpack/lib/normalize-options.ts b/packages/webpack/src/executors/webpack/lib/normalize-options.ts index 6106185a0d17c3..66893ea592164f 100644 --- a/packages/webpack/src/executors/webpack/lib/normalize-options.ts +++ b/packages/webpack/src/executors/webpack/lib/normalize-options.ts @@ -12,14 +12,13 @@ export function normalizeOptions( projectRoot: string, sourceRoot: string ): NormalizedWebpackExecutorOptions { - return { + const normalizedOptions = { ...options, root, projectRoot, sourceRoot, target: options.target ?? 'web', outputFileName: options.outputFileName ?? 'main.js', - assets: normalizeAssets(options.assets, root, sourceRoot), webpackConfig: normalizePluginPath(options.webpackConfig, root), optimization: typeof options.optimization !== 'object' @@ -29,6 +28,14 @@ export function normalizeOptions( } : options.optimization, }; + if (options.assets) { + normalizedOptions.assets = normalizeAssets( + options.assets, + root, + sourceRoot + ); + } + return normalizedOptions as NormalizedWebpackExecutorOptions; } export function normalizePluginPath(pluginPath: void | string, root: string) { diff --git a/packages/webpack/src/executors/webpack/schema.d.ts b/packages/webpack/src/executors/webpack/schema.d.ts index 22dd3c25bb13d6..79a8257aac2ea5 100644 --- a/packages/webpack/src/executors/webpack/schema.d.ts +++ b/packages/webpack/src/executors/webpack/schema.d.ts @@ -47,8 +47,10 @@ 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; + main?: string; memoryLimit?: number; namedChunks?: boolean; optimization?: boolean | OptimizationOptions; @@ -61,9 +63,9 @@ export interface WebpackExecutorOptions { runtimeChunk?: boolean; sourceMap?: boolean | 'hidden'; statsJson?: boolean; - target?: 'node' | 'web' | 'webworker'; + target?: string; transformers?: TransformerEntry[]; - tsConfig: string; + tsConfig?: string; vendorChunk?: boolean; verbose?: boolean; watch?: boolean; diff --git a/packages/webpack/src/executors/webpack/schema.json b/packages/webpack/src/executors/webpack/schema.json index befe0e7e5960f6..885df98c244ec4 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,18 +264,16 @@ }, "additionalProperties": false, "required": ["replace", "with"] - }, - "default": [] + } }, "buildLibsFromSource": { "type": "boolean", - "description": "Read buildable libraries from source instead of building them separately.", + "description": "Read buildable libraries from source instead of building them separately. If set to `false`, the `tsConfig` option must also be set to remap paths.", "default": true }, "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", @@ -321,7 +296,7 @@ "x-completion-type": "file" } }, - "required": ["tsConfig", "main"], + "required": [], "definitions": { "assetPattern": { "oneOf": [ diff --git a/packages/webpack/src/executors/webpack/webpack.impl.ts b/packages/webpack/src/executors/webpack/webpack.impl.ts index 4bd99f334887d9..efa7edbfc5d85a 100644 --- a/packages/webpack/src/executors/webpack/webpack.impl.ts +++ b/packages/webpack/src/executors/webpack/webpack.impl.ts @@ -9,25 +9,25 @@ import { switchMap, tap, } from 'rxjs/operators'; -import { join, resolve } from 'path'; +import { resolve } from 'path'; import { calculateProjectBuildableDependencies, createTmpTsConfig, } from '@nx/js/src/utils/buildable-libs-utils'; - -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'; +import { withNx } from '../../utils/with-nx'; +import { getRootTsConfigPath } from '@nx/js'; async function getWebpackConfigs( options: NormalizedWebpackExecutorOptions, - projectRoot: string, context: ExecutorContext ): Promise { if (options.isolatedConfig && !options.webpackConfig) { @@ -36,34 +36,30 @@ async function getWebpackConfigs( ); } - let customWebpack = null; - if (options.webpackConfig && options.tsConfig) { - customWebpack = resolveCustomWebpackConfig( + let userDefinedWebpackConfig = null; + if (options.webpackConfig) { + userDefinedWebpackConfig = resolveUserDefinedWebpackConfig( options.webpackConfig, - options.tsConfig.startsWith(context.root) - ? options.tsConfig - : join(context.root, options.tsConfig) + getRootTsConfigPath() ); - if (typeof customWebpack.then === 'function') { - customWebpack = await customWebpack; + if (typeof userDefinedWebpackConfig.then === 'function') { + userDefinedWebpackConfig = await userDefinedWebpackConfig; } } - const config = options.isolatedConfig - ? {} - : getWebpackConfig(context, options); + const config = options.isolatedConfig ? {} : withNx(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,8 +82,8 @@ export async function* webpackExecutor( _options: WebpackExecutorOptions, context: ExecutorContext ): AsyncGenerator { - // Pass to NxWebpackPlugin so we can get the CLI overrides. - process.env['NX_WEBPACK_EXECUTOR_RAW_OPTIONS'] = JSON.stringify(_options); + // Default to production build. + process.env['NODE_ENV'] ||= 'production'; const metadata = context.projectsConfigurations.projects[context.projectName]; const sourceRoot = metadata.sourceRoot; @@ -118,11 +114,6 @@ export async function* webpackExecutor( ); return { success: false, - outfile: resolve( - context.root, - options.outputPath, - options.outputFileName - ), options, }; } @@ -157,7 +148,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( @@ -184,6 +175,8 @@ export async function* webpackExecutor( const success = results.every( (result) => Boolean(result) && !result.hasErrors() ); + // TODO(jack): This should read output from webpack config if provided. + // The outfile is only used by NestJS, where `@nx/js:node` executor requires it to run the file. return { success, outfile: resolve( diff --git a/packages/webpack/src/generators/configuration/configuration.spec.ts b/packages/webpack/src/generators/configuration/configuration.spec.ts index 0430b754cd35f9..de428685dabb2d 100644 --- a/packages/webpack/src/generators/configuration/configuration.spec.ts +++ b/packages/webpack/src/generators/configuration/configuration.spec.ts @@ -32,7 +32,8 @@ describe('webpackProject', () => { outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { - main: 'libs/mypkg/src/main.ts', + outputPath: 'dist/libs/mypkg', + webpackConfig: 'libs/mypkg/webpack.config.js', }, }, }); @@ -44,15 +45,19 @@ describe('webpackProject', () => { main: 'libs/mypkg/index.ts', }); - const project = readProjectConfiguration(tree, 'mypkg'); + expect(tree.read('libs/mypkg/webpack.config.js', 'utf-8')).toContain( + `main: 'libs/mypkg/index.ts'` + ); + const project = readProjectConfiguration(tree, 'mypkg'); expect(project.targets).toMatchObject({ build: { executor: '@nx/webpack:webpack', outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { - main: 'libs/mypkg/index.ts', + outputPath: 'dist/libs/mypkg', + webpackConfig: 'libs/mypkg/webpack.config.js', }, }, }); @@ -64,15 +69,19 @@ describe('webpackProject', () => { tsConfig: 'libs/mypkg/tsconfig.custom.json', }); - const project = readProjectConfiguration(tree, 'mypkg'); + expect(tree.read('libs/mypkg/webpack.config.js', 'utf-8')).toContain( + `tsConfig: 'libs/mypkg/tsconfig.custom.json'` + ); + const project = readProjectConfiguration(tree, 'mypkg'); expect(project.targets).toMatchObject({ build: { executor: '@nx/webpack:webpack', outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { - tsConfig: 'libs/mypkg/tsconfig.custom.json', + outputPath: 'dist/libs/mypkg', + webpackConfig: 'libs/mypkg/webpack.config.js', }, }, }); diff --git a/packages/webpack/src/generators/configuration/configuration.ts b/packages/webpack/src/generators/configuration/configuration.ts index 4d2a953f955786..9fbdb907b3199e 100644 --- a/packages/webpack/src/generators/configuration/configuration.ts +++ b/packages/webpack/src/generators/configuration/configuration.ts @@ -10,6 +10,7 @@ import { import { webpackInitGenerator } from '../init/init'; import { ConfigurationGeneratorSchema } from './schema'; import { WebpackExecutorOptions } from '../../executors/webpack/schema'; +import { hasPlugin } from '../../utils/has-plugin'; export async function configurationGenerator( tree: Tree, @@ -20,9 +21,12 @@ export async function configurationGenerator( skipFormat: true, }); checkForTargetConflicts(tree, options); - addBuildTarget(tree, options); - if (options.devServer) { - addServeTarget(tree, options); + + if (!hasPlugin(tree)) { + addBuildTarget(tree, options); + if (options.devServer) { + addServeTarget(tree, options); + } } if (!options.skipFormat) { @@ -56,12 +60,7 @@ function checkForTargetConflicts( function addBuildTarget(tree: Tree, options: ConfigurationGeneratorSchema) { const project = readProjectConfiguration(tree, options.project); const buildOptions: WebpackExecutorOptions = { - target: options.target, outputPath: joinPathFragments('dist', project.root), - compiler: options.compiler ?? 'swc', - main: options.main ?? joinPathFragments(project.root, 'src/main.ts'), - tsConfig: - options.tsConfig ?? joinPathFragments(project.root, 'tsconfig.app.json'), webpackConfig: joinPathFragments(project.root, 'webpack.config.js'), }; @@ -82,14 +81,21 @@ function addBuildTarget(tree: Tree, options: ConfigurationGeneratorSchema) { tree.write( joinPathFragments(project.root, 'webpack.config.js'), ` -const { composePlugins, withNx, withWeb } = require('@nx/webpack'); +const { composePlugins, withNx } = require('@nx/webpack'); // Nx plugins for webpack. -module.exports = composePlugins(withNx(), withWeb(), (config) => { - // Update the webpack config as needed here. - // e.g. \`config.plugins.push(new MyPlugin())\` - return config; -}); +module.exports = composePlugins( + withNx({ + target: 'web' + ${options.main ? `main: '${options.main}',` : ''} + ${options.tsConfig ? `tsConfig: '${options.tsConfig}',` : ''} + }), + (config) => { + // Update the webpack config as needed here. + // e.g. \`config.plugins.push(new MyPlugin())\` + return config; + } +); ` ); } else { @@ -99,11 +105,18 @@ module.exports = composePlugins(withNx(), withWeb(), (config) => { const { composePlugins, withNx } = require('@nx/webpack'); // Nx plugins for webpack. -module.exports = composePlugins(withNx(), (config) => { - // Update the webpack config as needed here. - // e.g. \`config.plugins.push(new MyPlugin())\` - return config; -}); +module.exports = composePlugins( + withNx({ + target: 'node', + ${options.main ? `main: '${options.main}',` : ''} + ${options.tsConfig ? `tsConfig: '${options.tsConfig}',` : ''} + }), + (config) => { + // Update the webpack config as needed here. + // e.g. \`config.plugins.push(new MyPlugin())\` + return config; + } +); ` ); } @@ -120,10 +133,6 @@ module.exports = composePlugins(withNx(), (config) => { production: { optimization: true, outputHashing: options.target === 'web' ? 'all' : 'none', - sourceMap: false, - namedChunks: false, - extractLicenses: true, - vendorChunk: false, }, }, }, diff --git a/packages/webpack/src/generators/init/init.ts b/packages/webpack/src/generators/init/init.ts index ea94976c026b68..5d5cc36a1b2a06 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'; @@ -17,9 +19,10 @@ import { tsLibVersion, 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 shouldAddPlugin = process.env.NX_PCV3 === 'true'; const tasks: GeneratorCallback[] = []; const devDependencies = { '@nx/webpack': nxVersion, @@ -47,14 +50,40 @@ export async function webpackInitGenerator(tree: Tree, schema: Schema) { await formatFiles(tree); } - const baseInstalTask = addDependenciesToPackageJson( + const baseInstallTask = addDependenciesToPackageJson( tree, {}, devDependencies ); - tasks.push(baseInstalTask); + tasks.push(baseInstallTask); + + if (shouldAddPlugin) 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/migrations/update-17-2-0/update-17-2-0-migrate-webpack-config.ts b/packages/webpack/src/migrations/update-17-2-0/update-17-2-0-migrate-webpack-config.ts new file mode 100644 index 00000000000000..a17b2e0ce8f6cc --- /dev/null +++ b/packages/webpack/src/migrations/update-17-2-0/update-17-2-0-migrate-webpack-config.ts @@ -0,0 +1,4 @@ +export default async function update(): Promise { + // TODO(wip): Replace `withWeb` with `withNx({ target: 'web' })` + // TODO(wip): Move options from `withReact` to `withNx` -- except `svgr` +} 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..c5c381b3290a30 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 @@ -8,6 +8,7 @@ import { WebpackOptionsNormalized, WebpackPluginInstance, } from 'webpack'; +import { getRootTsConfigPath } from '@nx/js'; import { StatsJsonPlugin } from '../../stats-json-plugin'; import { GeneratePackageJsonPlugin } from '../../generate-package-json-plugin'; @@ -28,6 +29,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,8 +45,20 @@ 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 ??= []; + + const tsConfig = options.tsConfig ?? getRootTsConfigPath(); const plugins: WebpackPluginInstance[] = [ - new NxTsconfigPathsWebpackPlugin(options), + new NxTsconfigPathsWebpackPlugin({ tsConfig }), ]; const executorContext: Partial = { projectName: options.projectName, @@ -55,9 +72,9 @@ export function applyBaseConfig( plugins.push( new ForkTsCheckerWebpackPlugin({ typescript: { - configFile: path.isAbsolute(options.tsConfig) - ? options.tsConfig - : path.join(options.root, options.tsConfig), + configFile: path.isAbsolute(tsConfig) + ? tsConfig + : path.join(options.root, tsConfig), memoryLimit: options.memoryLimit || 2018, }, }) @@ -141,7 +158,7 @@ export function applyBaseConfig( ); } if (options.generatePackageJson && executorContext) { - plugins.push(new GeneratePackageJsonPlugin(options)); + plugins.push(new GeneratePackageJsonPlugin({ ...options, tsConfig })); } if (options.statsJson) { @@ -231,13 +248,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..49933fe07e66e7 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,16 @@ 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.runtimeChunk ??= true; // need this for HMR and other things to work + 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..8790ced6d74cc5 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 @@ -38,12 +38,8 @@ export interface OptimizationOptions { } export interface NxWebpackPluginOptions { - // Required options - main: string; - outputPath: string; - tsConfig: string; - - // Optional options + main?: string; + tsConfig?: string; additionalEntryPoints?: AdditionalEntryPoint[]; assets?: Array; babelConfig?: string; @@ -66,6 +62,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..2b0acc773573d0 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,15 @@ 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 not in an Nx task, we're building inferred targets, so skip normalizing build options. + if (process.env['NX_TASK_TARGET_PROJECT']) { + this.options = normalizeOptions(options); + } } apply(compiler: Compiler): void { @@ -35,10 +38,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,14 +51,9 @@ export class NxWebpackPlugin { useNormalizedEntry: true, }); } - } - private readExecutorOptions() { - const fromExecutor = process.env['NX_WEBPACK_EXECUTOR_RAW_OPTIONS'] ?? '{}'; - try { - return JSON.parse(fromExecutor); - } catch { - return {}; + if (this.options.deleteOutputPath) { + deleteOutputDir(this.options.root, this.options.outputPath); } } } diff --git a/packages/webpack/src/plugins/plugin.ts b/packages/webpack/src/plugins/plugin.ts new file mode 100644 index 00000000000000..d59ddaed818c90 --- /dev/null +++ b/packages/webpack/src/plugins/plugin.ts @@ -0,0 +1,224 @@ +import { + CreateNodes, + CreateNodesContext, + joinPathFragments, + TargetConfiguration, +} 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 { applyBaseConfigOptionsFromLastInvocation } from './nx-webpack-plugin/lib/apply-base-config'; +import { existsSync, readdirSync } from 'fs'; +import { NxWebpackPlugin } from './nx-webpack-plugin/nx-webpack-plugin'; +import { readWebpackOptions } from '../utils/webpack/read-webpack-options'; +import { resolveUserDefinedWebpackConfig } from '../utils/webpack/resolve-user-defined-webpack-config'; +import { getRootTsConfigPath } from '@nx/js'; + +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: CreateNodes = [ + '**/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 webpackConfig = resolveUserDefinedWebpackConfig( + join(context.workspaceRoot, configFilePath), + getRootTsConfigPath() + ); + const webpackOptions = await readWebpackOptions(webpackConfig); + + let target: string; + let outputPath: string; + + outputPath = + webpackOptions.output?.path ?? '{workspaceRoot}/dist/{projectRoot}'; + target = + typeof webpackOptions?.target == 'string' ? webpackOptions.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..a83572189699ee 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 = 'nxWebpackComposablePlugin'; + +export function isNxWebpackComposablePlugin( + a: unknown +): a is AsyncNxComposableWebpackPlugin { + 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/has-plugin.ts b/packages/webpack/src/utils/has-plugin.ts new file mode 100644 index 00000000000000..298d5481fbccd4 --- /dev/null +++ b/packages/webpack/src/utils/has-plugin.ts @@ -0,0 +1,10 @@ +import { readNxJson, Tree } from '@nx/devkit'; + +export function hasPlugin(tree: Tree) { + const nxJson = readNxJson(tree); + return !!nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/webpack/plugin' + : p.plugin === '@nx/webpack/plugin' + ); +} diff --git a/packages/webpack/src/utils/webpack/read-webpack-options.ts b/packages/webpack/src/utils/webpack/read-webpack-options.ts new file mode 100644 index 00000000000000..dae072a0fb6255 --- /dev/null +++ b/packages/webpack/src/utils/webpack/read-webpack-options.ts @@ -0,0 +1,44 @@ +import { isNxWebpackComposablePlugin } from '../config'; +import { Configuration } from 'webpack'; + +/** + * Reads the webpack options from a give webpack configuration. The configuration can be: + * 1. A standard config object + * 2. A standard function that returns a config object (webpack.js.org/configuration/configuration-types/#exporting-a-function) + * 3. A Nx-specific composable function that takes Nx context, webpack config, and returns the config object. + * + * @param webpackConfig + */ +export async function readWebpackOptions( + webpackConfig: unknown +): Promise { + let config: Configuration; + if (isNxWebpackComposablePlugin(webpackConfig)) { + config = await webpackConfig( + {}, + { + // These values are only used during build-time, so passing stubs here just to read out + // the returned config object. + options: { + root: '', + projectRoot: '', + sourceRoot: '', + outputFileName: undefined, + outputPath: undefined, + assets: undefined, + }, + context: { root: undefined, cwd: undefined, isVerbose: false }, + } + ); + } else if (typeof webpackConfig === 'function') { + config = await webpackConfig( + { + production: true, // we want the production build options + }, + {} + ); + } else { + config = webpackConfig; + } + 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..599033e0d5b033 100644 --- a/packages/webpack/src/utils/with-nx.ts +++ b/packages/webpack/src/utils/with-nx.ts @@ -1,38 +1,76 @@ import { Configuration } from 'webpack'; -import { NxWebpackExecutionContext, NxWebpackPlugin } from './config'; +import { + NxComposableWebpackPlugin, + nxWebpackComposablePlugin, + NxWebpackExecutionContext, +} from './config'; import { applyBaseConfig } from '../plugins/nx-webpack-plugin/lib/apply-base-config'; +import { NxWebpackPluginOptions } from '../plugins/nx-webpack-plugin/nx-webpack-plugin-options'; +import { applyWebConfig } from '../plugins/nx-webpack-plugin/lib/apply-web-config'; +import { normalizeAssets } from '../plugins/nx-webpack-plugin/lib/normalize-options'; const processed = new Set(); -export interface WithNxOptions { - skipTypeChecking?: boolean; -} +export type WithNxOptions = Partial; + +// Used by `@nx/webpack/plugin` to determine options such as main, tsConfig, etc. +export let withNxOptionsFromLastInvocation: null | WithNxOptions = null; /** * @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 => { + withNxOptionsFromLastInvocation = pluginOptions; + return function configure( + config: Configuration, + { options, context }: NxWebpackExecutionContext + ): Configuration { + if (processed.has(config)) return config; + + applyBaseConfig( + { + ...options, + ...pluginOptions, + assets: options.assets + ? options.assets + : normalizeAssets( + pluginOptions.assets, + options.root, + options.sourceRoot + ), + root: context.root, + projectName: context.projectName, + targetName: context.targetName, + configurationName: context.configurationName, + projectGraph: context.projectGraph, + }, + config + ); - applyBaseConfig( - { - ...options, - ...pluginOptions, - root: context.root, - projectName: context.projectName, - targetName: context.targetName, - configurationName: context.configurationName, - projectGraph: context.projectGraph, - }, - config - ); + if ( + pluginOptions?.target === 'web' || + pluginOptions?.target === 'webworker' + ) { + 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, + } +); diff --git a/packages/webpack/src/utils/with-web.ts b/packages/webpack/src/utils/with-web.ts index a3d3f30c00f081..08f629cb1e6481 100644 --- a/packages/webpack/src/utils/with-web.ts +++ b/packages/webpack/src/utils/with-web.ts @@ -1,60 +1,22 @@ import { Configuration } from 'webpack'; +import { logger } from '@nx/devkit'; -import { NxWebpackExecutionContext, NxWebpackPlugin } from './config'; -import { - ExtraEntryPointClass, - NormalizedWebpackExecutorOptions, -} from '../executors/webpack/schema'; -import { applyWebConfig } from '../plugins/nx-webpack-plugin/lib/apply-web-config'; - -const processed = new Set(); - -export interface WithWebOptions { - baseHref?: string; - crossOrigin?: 'none' | 'anonymous' | 'use-credentials'; - deployUrl?: string; - extractCss?: boolean; - generateIndexHtml?: boolean; - index?: string; - postcssConfig?: string; - scripts?: Array; - stylePreprocessorOptions?: any; - styles?: Array; - subresourceIntegrity?: boolean; - ssr?: boolean; -} - -// Omit deprecated options -export type MergedOptions = Omit< - NormalizedWebpackExecutorOptions, - keyof WithWebOptions -> & - WithWebOptions; +import { NxComposableWebpackPlugin, nxWebpackComposablePlugin } from './config'; /** - * @param {WithWebOptions} pluginOptions - * @returns {NxWebpackPlugin} + * @param {any} pluginOptions + * @returns {NxComposableWebpackPlugin} */ -export function withWeb(pluginOptions: WithWebOptions = {}): NxWebpackPlugin { - 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 - ); - - processed.add(config); - return config; - }; -} +export const withWeb = Object.assign( + (_pluginOptions: any): NxComposableWebpackPlugin => { + return function configure(config: Configuration): Configuration { + logger.warn( + `withWeb() is deprecated. Use withNx({ target: 'web' }) instead.` + ); + return config; + }; + }, + { + [nxWebpackComposablePlugin]: true, + } +);