@nx/webpack 24.0.0-beta.1 → 24.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +0 -1
  3. package/dist/internal.d.ts +3 -0
  4. package/dist/internal.js +7 -1
  5. package/dist/src/executors/dev-server/dev-server.impl.js +38 -30
  6. package/dist/src/generators/configuration/configuration.js +14 -132
  7. package/dist/src/generators/configuration/schema.d.ts +0 -1
  8. package/dist/src/generators/convert-config-to-webpack-plugin/convert-config-to-webpack-plugin.js +82 -49
  9. package/dist/src/generators/convert-config-to-webpack-plugin/lib/extract-webpack-options.d.ts +1 -0
  10. package/dist/src/generators/convert-config-to-webpack-plugin/lib/extract-webpack-options.js +31 -2
  11. package/dist/src/generators/convert-config-to-webpack-plugin/schema.json +2 -2
  12. package/dist/src/generators/convert-to-inferred/convert-to-inferred.js +5 -4
  13. package/dist/src/generators/convert-to-inferred/utils/build-post-target-transformer.js +13 -5
  14. package/dist/src/generators/convert-to-inferred/utils/serve-post-target-transformer.js +8 -14
  15. package/dist/src/generators/init/init.js +45 -54
  16. package/dist/src/generators/init/schema.d.ts +0 -1
  17. package/dist/src/migrations/update-24-0-0/finish-inferred-conversion.md +11 -0
  18. package/dist/src/migrations/update-24-0-0/inventory.d.ts +9 -0
  19. package/dist/src/migrations/update-24-0-0/inventory.js +53 -0
  20. package/dist/src/migrations/update-24-0-0/migrate-compose-plugins.d.ts +10 -0
  21. package/dist/src/migrations/update-24-0-0/migrate-compose-plugins.js +60 -0
  22. package/dist/src/migrations/update-24-0-0/migrate-compose-plugins.md +98 -0
  23. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.d.ts +6 -0
  24. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.js +36 -0
  25. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.md +92 -0
  26. package/dist/src/plugins/nx-webpack-plugin/lib/normalize-options.d.ts +6 -0
  27. package/dist/src/plugins/nx-webpack-plugin/lib/normalize-options.js +29 -24
  28. package/dist/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin.js +12 -1
  29. package/dist/src/utils/config.d.ts +8 -12
  30. package/dist/src/utils/config.js +15 -68
  31. package/dist/src/utils/deprecation.d.ts +1 -6
  32. package/dist/src/utils/deprecation.js +4 -27
  33. package/dist/src/utils/e2e-web-server-info-utils.d.ts +1 -1
  34. package/dist/src/utils/e2e-web-server-info-utils.js +2 -2
  35. package/dist/src/utils/with-nx.d.ts +5 -9
  36. package/dist/src/utils/with-nx.js +5 -32
  37. package/dist/src/utils/with-web.d.ts +5 -9
  38. package/dist/src/utils/with-web.js +5 -24
  39. package/executors.json +0 -10
  40. package/migrations.json +13 -0
  41. package/package.json +4 -4
  42. package/dist/src/executors/webpack/lib/run-webpack.d.ts +0 -3
  43. package/dist/src/executors/webpack/lib/run-webpack.js +0 -36
  44. package/dist/src/executors/webpack/schema.json +0 -459
  45. package/dist/src/executors/webpack/webpack.impl.d.ts +0 -13
  46. package/dist/src/executors/webpack/webpack.impl.js +0 -117
  47. package/dist/src/utils/has-plugin.d.ts +0 -2
  48. package/dist/src/utils/has-plugin.js +0 -10
@@ -16,7 +16,7 @@ function servePostTargetTransformerFactory(migrationContext) {
16
16
  projectName: projectDetails.projectName,
17
17
  projectRoot: projectDetails.root,
18
18
  };
19
- const { devServerOptions, webpackConfigPath } = await processOptions(tree, target, context);
19
+ const { devServerOptions, webpackConfigPath } = await processOptions(target, context);
20
20
  updateWebpackConfig(tree, webpackConfigPath, devServerOptions, context);
21
21
  if (target.outputs) {
22
22
  (0, internal_1.processTargetOutputs)(target, [], inferredTarget, {
@@ -27,20 +27,14 @@ function servePostTargetTransformerFactory(migrationContext) {
27
27
  return target;
28
28
  };
29
29
  }
30
- async function processOptions(tree, target, context) {
31
- const executorContext = {
32
- cwd: process.cwd(),
33
- nxJsonConfiguration: (0, devkit_1.readJson)(tree, 'nx.json'),
34
- projectGraph: context.projectGraph,
35
- projectName: context.projectName,
36
- projectsConfigurations: Object.entries(context.projectGraph.nodes).reduce((acc, [projectName, project]) => {
37
- acc.projects[projectName] = project.data;
38
- return acc;
39
- }, { version: 1, projects: {} }),
40
- root: context.workspaceRoot,
30
+ async function processOptions(target, context) {
31
+ const buildTarget = (0, devkit_1.parseTargetString)(target.options.buildTarget, context.projectGraph);
32
+ const buildConfiguration = context.projectGraph.nodes[buildTarget.project].data.targets[buildTarget.target];
33
+ // The build executor is removed in v24; do not resolve its schema.
34
+ const buildOptions = {
35
+ ...buildConfiguration.options,
36
+ ...buildConfiguration.configurations?.[buildTarget.configuration ?? buildConfiguration.defaultConfiguration],
41
37
  };
42
- const buildTarget = (0, devkit_1.parseTargetString)(target.options.buildTarget, executorContext);
43
- const buildOptions = (0, devkit_1.readTargetOptions)(buildTarget, executorContext);
44
38
  // it must exist, we validated it in the project filter
45
39
  const webpackConfigPath = buildOptions.webpackConfig;
46
40
  const defaultOptions = extractDevServerOptions(target.options, context);
@@ -7,71 +7,62 @@ const devkit_1 = require("@nx/devkit");
7
7
  const plugin_1 = require("../../plugins/plugin");
8
8
  const versions_1 = require("../../utils/versions");
9
9
  function webpackInitGenerator(tree, schema) {
10
- return webpackInitGeneratorInternal(tree, { addPlugin: false, ...schema });
10
+ return webpackInitGeneratorInternal(tree, schema);
11
11
  }
12
12
  async function webpackInitGeneratorInternal(tree, schema) {
13
13
  (0, versions_1.assertSupportedWebpackVersion)(tree);
14
- const nxJson = (0, devkit_1.readNxJson)(tree);
15
- const addPluginDefault = process.env.NX_ADD_PLUGINS !== 'false' &&
16
- nxJson.useInferencePlugins !== false;
17
- schema.addPlugin ??= addPluginDefault;
18
- if (schema.addPlugin) {
19
- await (0, internal_1.addPlugin)(tree, await (0, devkit_1.createProjectGraphAsync)(), '@nx/webpack/plugin', plugin_1.createNodesV2, {
20
- buildTargetName: [
21
- 'build',
22
- 'webpack:build',
23
- 'build:webpack',
24
- 'webpack-build',
25
- 'build-webpack',
26
- ],
27
- serveTargetName: [
28
- 'serve',
29
- 'webpack:serve',
30
- 'serve:webpack',
31
- 'webpack-serve',
32
- 'serve-webpack',
33
- ],
34
- previewTargetName: [
35
- 'preview',
36
- 'webpack:preview',
37
- 'preview:webpack',
38
- 'webpack-preview',
39
- 'preview-webpack',
40
- ],
41
- buildDepsTargetName: [
42
- 'build-deps',
43
- 'webpack:build-deps',
44
- 'webpack-build-deps',
45
- ],
46
- watchDepsTargetName: [
47
- 'watch-deps',
48
- 'webpack:watch-deps',
49
- 'webpack-watch-deps',
50
- ],
51
- serveStaticTargetName: [
52
- 'serve-static',
53
- 'webpack:serve-static',
54
- 'serve-static:webpack',
55
- 'webpack-serve-static',
56
- 'serve-static-webpack',
57
- ],
58
- }, schema.updatePackageScripts);
59
- }
14
+ await (0, internal_1.addPlugin)(tree, await (0, devkit_1.createProjectGraphAsync)(), '@nx/webpack/plugin', plugin_1.createNodesV2, {
15
+ buildTargetName: [
16
+ 'build',
17
+ 'webpack:build',
18
+ 'build:webpack',
19
+ 'webpack-build',
20
+ 'build-webpack',
21
+ ],
22
+ serveTargetName: [
23
+ 'serve',
24
+ 'webpack:serve',
25
+ 'serve:webpack',
26
+ 'webpack-serve',
27
+ 'serve-webpack',
28
+ ],
29
+ previewTargetName: [
30
+ 'preview',
31
+ 'webpack:preview',
32
+ 'preview:webpack',
33
+ 'webpack-preview',
34
+ 'preview-webpack',
35
+ ],
36
+ buildDepsTargetName: [
37
+ 'build-deps',
38
+ 'webpack:build-deps',
39
+ 'webpack-build-deps',
40
+ ],
41
+ watchDepsTargetName: [
42
+ 'watch-deps',
43
+ 'webpack:watch-deps',
44
+ 'webpack-watch-deps',
45
+ ],
46
+ serveStaticTargetName: [
47
+ 'serve-static',
48
+ 'webpack:serve-static',
49
+ 'serve-static:webpack',
50
+ 'webpack-serve-static',
51
+ 'serve-static-webpack',
52
+ ],
53
+ }, schema.updatePackageScripts);
60
54
  let installTask = () => { };
61
55
  if (!schema.skipPackageJson) {
62
- // webpack and webpack-dev-server are peer dependencies, so ensure they are
63
- // installed (both the executor and inferred-plugin paths need them at the
64
- // user's runtime).
56
+ // webpack and webpack-dev-server are peer dependencies needed at the
57
+ // user's runtime.
65
58
  const devDependencies = {
66
59
  '@nx/webpack': versions_1.nxVersion,
67
60
  '@nx/web': versions_1.nxVersion,
68
61
  webpack: versions_1.webpackVersion,
69
62
  'webpack-dev-server': versions_1.webpackDevServerVersion,
70
- };
71
- if (schema.addPlugin) {
72
63
  // The inferred plugin runs the `webpack-cli` binary.
73
- devDependencies['webpack-cli'] = versions_1.webpackCliVersion;
74
- }
64
+ 'webpack-cli': versions_1.webpackCliVersion,
65
+ };
75
66
  // @nx/webpack depends on sass, which pulls in @parcel/watcher. Its install
76
67
  // script only builds from source when npm_config_build_from_source is set,
77
68
  // so skip it.
@@ -3,5 +3,4 @@ export interface Schema {
3
3
  skipPackageJson?: boolean;
4
4
  keepExistingVersions?: boolean;
5
5
  updatePackageScripts?: boolean;
6
- addPlugin?: boolean;
7
6
  }
@@ -0,0 +1,11 @@
1
+ # Replace removed @nx/webpack executors with inferred tasks
2
+
3
+ Nx 24 removed `@nx/webpack:webpack`, `@nx/webpack:dev-server` (including old `@nrwl` aliases).
4
+
5
+ Before changing anything, check for leftover work: a project target, a package.json target or a `targetDefaults` entry still uses a removed executor, or the advisory context reports a conversion error. If none applies, make no changes and stop. Treat each advisory-context item as pending work.
6
+
7
+ 1. Inspect all affected projects and workspace defaults. Repair config-loading errors first. Do not restore executor registrations or install older Nx packages.
8
+ 2. Register `@nx/webpack/plugin` with options/includes that preserve custom target names and mixed inferred/explicit projects. Replace remaining removed-executor targets, including those in package.json and filtered targetDefaults. Transfer their behavior before removing obsolete defaults. Preserve configurations, dependencies, cache inputs/outputs and environment behavior. Do not silently delete unsupported options or unrelated targets. Migrate old @nrwl aliases too.
9
+ 3. Run project graph creation with the daemon and graph cache disabled (`NX_DAEMON=false NX_CACHE_PROJECT_GRAPH=false`), using the workspace package manager. Inspect resolved project targets and verify each migrated entrypoint exists and is inferred (a thin alias to an inferred target is acceptable). Resolve graph errors before completing this migration, even if task validation was skipped. Configs must load for subsequent migrations.
10
+ 4. By default run relevant real builds/tests with task cache disabled, including alternate configurations. Honor an existing preference to skip expensive task validation; otherwise offer that option when validation is slow. Skipping expensive tasks never skips conversion or the graph check. Report each skipped task as unverified. Never weaken assertions or change application behavior to make checks pass.
11
+ 5. Report converted projects, graph validation, task results and any skipped checks. Unconverted targets or a broken graph mean the migration is incomplete. Investigate task failures and distinguish missing environment prerequisites from conversion regressions; do not claim a working workspace with unresolved regressions.
@@ -0,0 +1,9 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export interface HelperUsage {
3
+ name: string;
4
+ root: string;
5
+ configPaths: string[];
6
+ usesHelpers: boolean;
7
+ usesExecutor: boolean;
8
+ }
9
+ export declare function inventoryComposePlugins(tree: Tree): HelperUsage[];
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inventoryComposePlugins = inventoryComposePlugins;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const CONFIG_EXTENSIONS = ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts'];
6
+ const REMOVED_HELPERS = /\b(composePlugins|composePluginsSync|withNx|withWeb|withReact)\b/;
7
+ // Left by an earlier conversion; the wrapped `.old` config still calls the helpers.
8
+ const LEGACY_WRAPPER = /\buseLegacyNxPlugin\b/;
9
+ function inventoryComposePlugins(tree) {
10
+ const usages = [];
11
+ for (const [name, project] of (0, devkit_1.getProjects)(tree)) {
12
+ const configs = [...collectConfigPaths(tree, project)].map((path) => ({
13
+ path,
14
+ source: tree.read(path, 'utf-8') ?? '',
15
+ }));
16
+ const configPaths = configs
17
+ .filter(({ source }) => REMOVED_HELPERS.test(source) || LEGACY_WRAPPER.test(source))
18
+ .map(({ path }) => path);
19
+ if (!configPaths.length)
20
+ continue;
21
+ usages.push({
22
+ name,
23
+ root: project.root,
24
+ configPaths: configPaths.sort(),
25
+ usesHelpers: configs.some(({ source }) => REMOVED_HELPERS.test(source)),
26
+ usesExecutor: Object.values(project.targets ?? {}).some((target) => target.executor === '@nx/webpack:webpack'),
27
+ });
28
+ }
29
+ return usages;
30
+ }
31
+ function collectConfigPaths(tree, project) {
32
+ const paths = new Set();
33
+ for (const target of Object.values(project.targets ?? {})) {
34
+ for (const options of [
35
+ target.options,
36
+ ...Object.values(target.configurations ?? {}),
37
+ ]) {
38
+ const configured = options?.webpackConfig;
39
+ // A package path such as `@nx/react/plugins/webpack` resolves to the stubs
40
+ // rather than a workspace file; the prompt gives those projects a config.
41
+ if (typeof configured === 'string' && tree.exists(configured)) {
42
+ paths.add(configured);
43
+ }
44
+ }
45
+ }
46
+ for (const extension of CONFIG_EXTENSIONS) {
47
+ const conventional = `${project.root}/webpack.config.${extension}`;
48
+ if (tree.exists(conventional)) {
49
+ paths.add(conventional);
50
+ }
51
+ }
52
+ return paths;
53
+ }
@@ -0,0 +1,10 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function migrateComposePlugins(tree: Tree): Promise<{
3
+ skipAgentic: boolean;
4
+ nextSteps?: undefined;
5
+ agentContext?: undefined;
6
+ } | {
7
+ skipAgentic?: undefined;
8
+ nextSteps: string[];
9
+ agentContext: string[];
10
+ }>;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = migrateComposePlugins;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const convert_config_to_webpack_plugin_1 = require("../../generators/convert-config-to-webpack-plugin/convert-config-to-webpack-plugin");
6
+ const inventory_1 = require("./inventory");
7
+ const CONVERTER = '@nx/webpack:convert-config-to-webpack-plugin';
8
+ async function migrateComposePlugins(tree) {
9
+ const usages = (0, inventory_1.inventoryComposePlugins)(tree);
10
+ if (!usages.length) {
11
+ return { skipAgentic: true };
12
+ }
13
+ const converted = [];
14
+ const remaining = [];
15
+ for (const usage of usages) {
16
+ if (!usage.usesHelpers) {
17
+ converted.push(usage);
18
+ continue;
19
+ }
20
+ try {
21
+ await (0, convert_config_to_webpack_plugin_1.convertConfigToWebpackPluginGenerator)(tree, {
22
+ project: usage.name,
23
+ skipFormat: true,
24
+ });
25
+ converted.push(usage);
26
+ }
27
+ catch (e) {
28
+ remaining.push({ usage, reason: e.message });
29
+ }
30
+ }
31
+ if (converted.length) {
32
+ await (0, devkit_1.formatFiles)(tree);
33
+ }
34
+ return {
35
+ nextSteps: nextSteps(converted, remaining),
36
+ agentContext: agentContext(converted, remaining),
37
+ };
38
+ }
39
+ function nextSteps(converted, remaining) {
40
+ const steps = [];
41
+ if (converted.length) {
42
+ steps.push(`Inline the \`webpack.config.old.*\` files that ${CONVERTER} left behind in ${converted.length} project(s), then drop \`useLegacyNxPlugin\`.`);
43
+ }
44
+ if (remaining.length) {
45
+ steps.push(`Migrate ${remaining.length} project(s) off the removed webpack config helpers by hand: ${remaining
46
+ .map(({ usage }) => usage.name)
47
+ .join(', ')}.`);
48
+ }
49
+ return steps;
50
+ }
51
+ function agentContext(converted, remaining) {
52
+ const context = [];
53
+ if (converted.length) {
54
+ context.push(`${CONVERTER} converted these projects. Each one now has a standard config whose \`useLegacyNxPlugin\` call still loads the renamed \`.old\` config, so the helper callbacks it holds have to move into the new config before the \`.old\` file can go:`, ...converted.map((usage) => `- ${usage.name} (${usage.root}): ${usage.configPaths.join(', ')}`));
55
+ }
56
+ if (remaining.length) {
57
+ context.push(`${CONVERTER} could not convert these projects, so their configs are untouched and still call the removed helpers. Rewrite each one by hand:`, ...remaining.map(({ usage, reason }) => `- ${usage.name} (${usage.root}): ${usage.configPaths.join(', ')} - ${reason}`));
58
+ }
59
+ return context;
60
+ }
@@ -0,0 +1,98 @@
1
+ # Migrate webpack compose helpers
2
+
3
+ Nx v24 removed `composePlugins`, `composePluginsSync`, `withNx`, `withWeb`, and webpack
4
+ `withReact`. Their exports remain as inert stubs so existing configs still load while Nx
5
+ builds the project graph. They do not configure builds, and a config that loads is not a
6
+ working build.
7
+
8
+ `@nx/webpack:convert-config-to-webpack-plugin` already ran. The agent context lists the
9
+ projects it converted, including configs an earlier conversion left wrapped in
10
+ `useLegacyNxPlugin`, and, separately, the projects it could not convert with the reason
11
+ for each. Work only on those projects and the files they import. Do not re-inventory the
12
+ workspace, and do not run the converter again.
13
+
14
+ Preserve the installed webpack version, bundler, targets, configurations, and commands.
15
+ Nx 24 also removed the `@nx/webpack:webpack` executor. Its targets are converted to inferred
16
+ `@nx/webpack/plugin` tasks by a separate migration, so do not add it back or keep a target on it.
17
+
18
+ ## Baseline
19
+
20
+ This workspace is already on v24, so the old helpers no longer build here. To compare
21
+ against the previous behavior, check out the commit before the upgrade in a separate git
22
+ worktree, install with its lockfile, and build there. Do not downgrade this workspace. If
23
+ you cannot build a baseline, say so in your report rather than calling the comparison a
24
+ pass. Record build commands, output locations, assets, entry points, and any observable
25
+ custom behavior.
26
+
27
+ ## Projects the converter handled
28
+
29
+ Each one now has a standard config built around `NxAppWebpackPlugin`, plus a
30
+ `useLegacyNxPlugin(require('./webpack.config.old.*'), options)` entry that still loads the
31
+ renamed old config. The old config composes its remaining callbacks through a local
32
+ `composeLegacyPlugins` helper the converter inlined. Two things are left:
33
+
34
+ - Move what the old config still does into the new config, then delete the `.old` file and
35
+ drop `useLegacyNxPlugin`.
36
+ - `useLegacyNxPlugin` runs at `beforeCompile`. That is too late to register rules, entries,
37
+ or plugins, so anything the old config set through those is already not applying the way
38
+ it used to. Check each one against the baseline instead of trusting the converted build.
39
+
40
+ Also check the options the converter copied into the new config. It reads them out of the
41
+ old `withNx` call as written, so values that depended on the executor or on environment
42
+ variables are not represented, and workspace-relative paths may need adjusting: native
43
+ plugin options resolve against the project and config directory, while executor options
44
+ were workspace-relative.
45
+
46
+ ## Projects the converter skipped
47
+
48
+ The agent context gives a reason per project. Those configs are untouched and still call
49
+ the removed helpers, so rewrite each one.
50
+
51
+ Use a standard webpack configuration with `NxAppWebpackPlugin` from
52
+ `@nx/webpack/app-plugin`. For React apps also use `NxReactWebpackPlugin` from
53
+ `@nx/react/webpack-plugin`. Do not add React configuration to Node or plain web apps, and
54
+ do not change frameworks or upgrade dependencies to simplify the rewrite.
55
+
56
+ Map the effective old options onto those plugins and standard webpack properties, reading
57
+ the installed plugin types rather than assuming. Preserve precedence between target
58
+ options, named configurations, helper options, and custom callbacks. Preserve compiler
59
+ choice, aliases, CSS, SVG rules, assets, externals, file replacements, source maps,
60
+ optimization, public paths, output filenames, and dev-server behavior. Set output hashing
61
+ explicitly where the old helper defaults differ from the plugin defaults; a build that
62
+ succeeds with renamed entry files is not output parity.
63
+
64
+ A `webpackConfig` target option naming a package path rather than a local file, such as
65
+ `@nx/react/plugins/webpack`, resolves to the stubs. Give that project its own config file.
66
+
67
+ Inferred targets run the webpack CLI, which calls a function export with `(env, argv)`.
68
+ Represent the options the executor used to supply in the config itself. Use config
69
+ functions or separate configs where values vary by configuration.
70
+
71
+ For custom composable functions, move the function into user-owned code and supply the
72
+ options and context it needs explicitly. A local adapter exposing `apply(compiler)` can
73
+ work, but an `apply` object belongs in `config.plugins`; exporting it as the whole config
74
+ is invalid, and a top-level config function must return a config object or supported array.
75
+ Preserve execution order and whether each callback mutates its input or returns a
76
+ replacement. Await asynchronous transforms and propagate failures rather than leaving a
77
+ pending promise.
78
+
79
+ ## Module Federation
80
+
81
+ Projects using Nx Module Federation are out of scope here. `@nx/module-federation` ships
82
+ its own v24 migration; follow that one. Do not remove federation, disable the project, or
83
+ report success for it from this migration.
84
+
85
+ ## Verify and finish
86
+
87
+ 1. Rebuild the project graph without its disk cache and confirm the same projects and
88
+ targets are still discoverable.
89
+ 2. Run each affected build, development and production and any alternate configurations,
90
+ without reusing cached task outputs. Exercise Node startup where it applies, plus tests
91
+ and dev-server or HMR behavior.
92
+ 3. Compare emitted files against the baseline: custom assets and markers, entry points,
93
+ aliases, CSS and SVG handling, externals, and output paths. Compare content where hashes
94
+ differ. A zero exit code is not enough.
95
+ 4. Search the active configs and their imports for the removed helpers again. An already
96
+ migrated config needs no further change.
97
+ 5. Report the commands you ran, the output you checked, and anything still unresolved. Do
98
+ not delete tests, custom behavior, or targets to get a green result.
@@ -0,0 +1,6 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function update(tree: Tree): Promise<{
3
+ nextSteps?: string[];
4
+ agentContext?: string[];
5
+ skipAgentic?: boolean;
6
+ }>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/devkit/internal");
6
+ const convert_config_to_webpack_plugin_1 = require("../../generators/convert-config-to-webpack-plugin/convert-config-to-webpack-plugin");
7
+ const inventory_1 = require("./inventory");
8
+ function update(tree) {
9
+ return (0, internal_1.migrateRemovedExecutors)(tree, [
10
+ '@nx/webpack:webpack',
11
+ '@nx/webpack:dev-server',
12
+ '@nrwl/webpack:webpack',
13
+ '@nrwl/webpack:dev-server',
14
+ ], async (tree, options) => {
15
+ await convertHelperConfigs(tree);
16
+ const { convertToInferred } = require('../../generators/convert-to-inferred/convert-to-inferred');
17
+ return convertToInferred(tree, options);
18
+ });
19
+ }
20
+ // convert-to-inferred skips configs built on the removed helpers, so convert
21
+ // those first. migrate-compose-plugins reports the `.old` configs left behind.
22
+ async function convertHelperConfigs(tree) {
23
+ for (const usage of (0, inventory_1.inventoryComposePlugins)(tree)) {
24
+ if (!usage.usesExecutor || !usage.usesHelpers)
25
+ continue;
26
+ try {
27
+ await (0, convert_config_to_webpack_plugin_1.convertConfigToWebpackPluginGenerator)(tree, {
28
+ project: usage.name,
29
+ skipFormat: true,
30
+ });
31
+ }
32
+ catch (e) {
33
+ devkit_1.logger.warn(`Could not convert the webpack config helpers in ${usage.name}: ${e.message}`);
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,92 @@
1
+ #### Convert `@nx/webpack` build and dev-server targets to inferred tasks
2
+
3
+ Nx 24 removes the `@nx/webpack:webpack` and `@nx/webpack:dev-server` executors and their `@nrwl` aliases. The migration registers `@nx/webpack/plugin` in `nx.json` and converts the targets that use those executors to inferred tasks. It moves the executor options into `webpack.config.*`. A target with nothing left to override is removed from project configuration. When several projects share the same remaining options, those options move to a `targetDefaults` entry scoped to the plugin. `targetDefaults` entries keyed by a removed executor are removed once no target uses that executor.
4
+
5
+ The migration lists the targets the converter cannot convert safely. In agentic runs, an AI prompt finishes them and validates the project graph.
6
+
7
+ The `@nx/webpack:ssr-dev-server` executor stays.
8
+
9
+ #### Sample code changes
10
+
11
+ The example shows the `build` target. Its options move into `webpack.config.*`, so only the configuration names stay on the target.
12
+
13
+ ##### Before
14
+
15
+ ```json title="apps/app1/project.json"
16
+ {
17
+ "name": "app1",
18
+ "targets": {
19
+ "build": {
20
+ "executor": "@nx/webpack:webpack",
21
+ "options": {
22
+ "webpackConfig": "apps/app1/webpack.config.js",
23
+ "outputPath": "dist/apps/app1",
24
+ "index": "apps/app1/src/index.html",
25
+ "main": "apps/app1/src/main.tsx",
26
+ "tsConfig": "apps/app1/tsconfig.app.json",
27
+ "assets": ["apps/app1/src/favicon.ico", "apps/app1/src/assets"],
28
+ "styles": ["apps/app1/src/styles.scss"],
29
+ "scripts": [],
30
+ "compiler": "babel",
31
+ "baseHref": "/"
32
+ },
33
+ "configurations": {
34
+ "development": {
35
+ "extractLicenses": false,
36
+ "optimization": false,
37
+ "sourceMap": true,
38
+ "vendorChunk": true
39
+ },
40
+ "production": {
41
+ "fileReplacements": [
42
+ {
43
+ "replace": "apps/app1/src/environments/environment.ts",
44
+ "with": "apps/app1/src/environments/environment.prod.ts"
45
+ }
46
+ ],
47
+ "optimization": true,
48
+ "outputHashing": "all",
49
+ "sourceMap": false,
50
+ "namedChunks": false,
51
+ "extractLicenses": true,
52
+ "vendorChunk": false
53
+ }
54
+ },
55
+ "defaultConfiguration": "production"
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ##### After
62
+
63
+ ```json title="apps/app1/project.json"
64
+ {
65
+ "name": "app1",
66
+ "targets": {
67
+ "build": {
68
+ "configurations": {
69
+ "development": {},
70
+ "production": {}
71
+ },
72
+ "defaultConfiguration": "production"
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ```json title="nx.json"
79
+ {
80
+ "plugins": [
81
+ {
82
+ "plugin": "@nx/webpack/plugin",
83
+ "options": {
84
+ "buildTargetName": "build",
85
+ "previewTargetName": "preview",
86
+ "serveStaticTargetName": "serve-static",
87
+ "serveTargetName": "serve"
88
+ }
89
+ }
90
+ ]
91
+ }
92
+ ```
@@ -1,4 +1,10 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { type Target } from '@nx/devkit';
1
3
  import { AssetGlobPattern, FileReplacement, NormalizedNxAppWebpackPluginOptions, NxAppWebpackPluginOptions } from '../nx-app-webpack-plugin-options';
4
+ export declare const webpackExecutorContext: AsyncLocalStorage<{
5
+ options: Partial<NxAppWebpackPluginOptions>;
6
+ target: Target;
7
+ }>;
2
8
  export declare function normalizeOptions(options: NxAppWebpackPluginOptions): NormalizedNxAppWebpackPluginOptions;
3
9
  export declare function normalizeAssets(assets: any[], root: string, sourceRoot: string, projectRoot: string, resolveRelativePathsToProjectRoot?: boolean): AssetGlobPattern[];
4
10
  export declare function normalizeFileReplacements(root: string, fileReplacements: FileReplacement[]): FileReplacement[];
@@ -1,20 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.webpackExecutorContext = void 0;
3
4
  exports.normalizeOptions = normalizeOptions;
4
5
  exports.normalizeAssets = normalizeAssets;
5
6
  exports.normalizeFileReplacements = normalizeFileReplacements;
7
+ const node_async_hooks_1 = require("node:async_hooks");
6
8
  const devkit_1 = require("@nx/devkit");
7
9
  const internal_1 = require("@nx/js/internal");
8
10
  const fs_1 = require("fs");
9
11
  const path_1 = require("path");
12
+ exports.webpackExecutorContext = new node_async_hooks_1.AsyncLocalStorage();
10
13
  function normalizeOptions(options) {
11
14
  const combinedPluginAndMaybeExecutorOptions = {};
12
15
  const isProd = process.env.NODE_ENV === 'production';
13
16
  // Since this is invoked by the executor, the graph has already been created and cached.
14
17
  const projectGraph = (0, devkit_1.readCachedProjectGraph)();
15
- const taskDetailsFromBuildTarget = process.env.NX_BUILD_TARGET
16
- ? (0, devkit_1.parseTargetString)(process.env.NX_BUILD_TARGET, projectGraph)
17
- : undefined;
18
+ const executorContext = exports.webpackExecutorContext.getStore();
19
+ const taskDetailsFromBuildTarget = executorContext?.target ??
20
+ (process.env.NX_BUILD_TARGET
21
+ ? (0, devkit_1.parseTargetString)(process.env.NX_BUILD_TARGET, projectGraph)
22
+ : undefined);
18
23
  const projectName = taskDetailsFromBuildTarget
19
24
  ? taskDetailsFromBuildTarget.project
20
25
  : process.env.NX_TASK_TARGET_PROJECT;
@@ -29,26 +34,15 @@ function normalizeOptions(options) {
29
34
  normalizeRelativePaths(projectNode.data.root, options);
30
35
  // Merge options from `@nx/webpack:webpack` into plugin options.
31
36
  // Options from `@nx/webpack:webpack` take precedence.
32
- const originalTargetOptions = targetConfig.options;
33
- if (configurationName) {
34
- Object.assign(originalTargetOptions, targetConfig.configurations?.[configurationName]);
35
- }
36
- // This could be called from dev-server which means we need to read `buildTarget` to get actual build options.
37
- // Otherwise, the options are passed from the `@nx/webpack:webpack` executor.
38
- if (originalTargetOptions.buildTarget) {
39
- const buildTargetOptions = targetConfig.options;
40
- if (configurationName) {
41
- Object.assign(buildTargetOptions, targetConfig.configurations?.[configurationName]);
42
- }
43
- Object.assign(combinedPluginAndMaybeExecutorOptions, options,
44
- // executor options take precedence (especially for overriding with CLI args)
45
- buildTargetOptions);
46
- }
47
- else {
48
- Object.assign(combinedPluginAndMaybeExecutorOptions, options,
49
- // executor options take precedence (especially for overriding with CLI args)
50
- originalTargetOptions);
51
- }
37
+ const targetOptions = {
38
+ ...targetConfig.options,
39
+ ...(configurationName
40
+ ? targetConfig.configurations?.[configurationName]
41
+ : undefined),
42
+ };
43
+ Object.assign(combinedPluginAndMaybeExecutorOptions, options, targetOptions);
44
+ const executorOptions = executorContext?.options;
45
+ Object.assign(combinedPluginAndMaybeExecutorOptions, Object.fromEntries(Object.entries(executorOptions ?? {}).filter(([key, value]) => value !== undefined && !isExecutorSchemaDefault(key, value))));
52
46
  const sourceRoot = (0, internal_1.getProjectSourceRoot)(projectNode.data);
53
47
  if (!combinedPluginAndMaybeExecutorOptions.main) {
54
48
  throw new Error(`Missing "main" option for the entry file. Set this option in your Nx webpack plugin.`);
@@ -56,7 +50,7 @@ function normalizeOptions(options) {
56
50
  return {
57
51
  ...combinedPluginAndMaybeExecutorOptions,
58
52
  assets: combinedPluginAndMaybeExecutorOptions.assets
59
- ? normalizeAssets(combinedPluginAndMaybeExecutorOptions.assets, devkit_1.workspaceRoot, sourceRoot, projectNode.data.root)
53
+ ? normalizeAssets(combinedPluginAndMaybeExecutorOptions.assets, devkit_1.workspaceRoot, sourceRoot, projectNode.data.root, executorOptions?.assets === undefined)
60
54
  : [],
61
55
  baseHref: combinedPluginAndMaybeExecutorOptions.baseHref ?? '/',
62
56
  buildLibsFromSource: combinedPluginAndMaybeExecutorOptions.buildLibsFromSource ?? true,
@@ -163,6 +157,17 @@ function normalizeRelativePaths(projectRoot, options) {
163
157
  }
164
158
  }
165
159
  }
160
+ // Executor-style callers pass options with the removed @nx/webpack:webpack
161
+ // schema defaults applied. Those defaults must not override the plugin's own
162
+ // options; target options still apply above.
163
+ const executorSchemaDefaults = {
164
+ outputFileName: 'main.js',
165
+ standardWebpackConfigFunction: false,
166
+ buildLibsFromSource: true,
167
+ };
168
+ function isExecutorSchemaDefault(key, value) {
169
+ return key in executorSchemaDefaults && executorSchemaDefaults[key] === value;
170
+ }
166
171
  function isRelativePath(val) {
167
172
  return (typeof val === 'string' &&
168
173
  (val.startsWith('./') ||